Search This Blog

Wednesday, 13 March 2013

My First JPA Application - 2

In the previous post I created  the project structure for my JPA application. Now to look deeper into the code. I decided to build a simple Pet-Owner relation. One owner can have multiple pets. The java classes for the same is as below:
The java classes are as below:
@Entity()
@Table(name = "OWNER",schema="firstOne")
public class Owner {

    private Long id; // This is used by hibernate as the Identifier
    private String name;
    private String userId; // This can represent a user uniquely (and cannot be
                            // changed)
    private Set<Pet> pets; // Every owner has a collection of pets ( One To Many
                            // Relation)

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return id;
    }

    @Column(name = "NAME")
    public String getName() {
        return name;
    }

    @Column(name = "USER_ID", insertable = true, updatable = false, 
                 length = 20, nullable = false, unique = true)
    public String getUserId() {
        return userId;
    }

    @OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, 
                 mappedBy = "owner", fetch = FetchType.LAZY, 
                 targetEntity = Pet.class)
    public Set<Pet> getPets() {
        return pets;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public void setPets(Set<Pet> pets) {
        this.pets = pets;
    }

    public void addPet(Pet pet) {
        if (null == pet) {
            throw new IllegalArgumentException("Null Pet not supported !!");
        }
        pet.setOwner(this);
        if (null == this.pets) {
            this.pets = new HashSet<Pet>();
        }
        this.getPets().add(pet);
    }

    @Override
    public String toString() {
        return "(Owner : [userId: " + this.getUserId() 
                + " ],[name: " + this.getName() + " ], [age:  " + this.getId() + "])";
    }

    @Override
    public boolean equals(Object obj) {
        // The equals method here does not depend on the Db Identifier
        boolean equal = false;
        if (obj instanceof Owner) {
            Owner otherOwner = (Owner) obj;
            equal = this.getUserId().equals(otherOwner.getUserId());
        }
        return equal;
    }

    @Override
    public int hashCode() {
        return this.getUserId().hashCode();
    }

}
Pet.java:
@Entity(name = "Pet")
@Table(name = "PET", schema="firstOne",uniqueConstraints = { 
               @UniqueConstraint(columnNames = { "TAG_ID" }) })
public class Pet {
    private Long id; // the Db identifier
    private String name;
    private int age;
    private String tagId; // a unique identifier (that can be changed)
    private Owner owner; // a reference to the owning record

    public void setId(Long id) {
        this.id = id;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return id;
    }

    @Column(name = "NAME", length = 12, nullable = false, 
             insertable = true, unique = false, updatable = true)
    public String getName() {
        return name;
    }

    @Column(name = "TAG_ID")
    public String getTagId() {
        return tagId;
    }

    @Column(name = "AGE")
    public int getAge() {
        return age;
    }

    @ManyToOne(fetch = FetchType.LAZY, optional = false, 
             targetEntity = Owner.class)
    @JoinColumn(name="OWNER_ID")
    public Owner getOwner() {
        return owner;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setTagId(String tagId) {
        this.tagId = tagId;
    }

    public void setOwner(Owner owner) {
        this.owner = owner;
    }

    @Override
    public String toString() {
        // avoided any reference to Owner here as that could result in extra
        // queries
        return "(Pet : [name: " + this.getName() 
                 + "[tagId: " + this.getTagId() + " ], [id:  " + this.getId() + "])";
    }

    @Override
    public boolean equals(Object obj) {
        // The equals method here does not depend on the DB Identifier
        boolean equal = false;
        if (obj instanceof Pet) {
            Pet otherPet = (Pet) obj;
            equal = this.getTagId().equals(otherPet.getTagId());
        }
        return equal;
    }

    @Override
    public int hashCode() {
        return this.getTagId().hashCode();
    }

}
As can be seen, the code uses annotations to provide information about the tables. All the annotations are JPA's standard set. I haven't used any Hibernate extensions.
The next step is to setup the configuration information:
Unlike hibernate, JPA has a very fixed way of providing the configuration information. The configuration start-point is a file "persistence.xml". This file needs to be placed in the META-INF folder on the classpath.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
    version="1.0">
    <persistence-unit name="firstOne">
        <properties>
            <property name="hibernate.ejb.cfgfile" value="/META-INF/hibernate.cfg.xml" />
        </properties>
    </persistence-unit>
</persistence>
In this we redirect the persistence unit to hibernate.cfg.xml for more detailed settings:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">org.postgresql.Driver</property>
        <property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
        <property name="hibernate.connection.username">postgres</property>
        <property name="hibernate.connection.password">root</property>
        <property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/pets</property>
        <property name="hibernate.hbm2ddl.auto">create</property>
        <property name="hibernate.generate_statistics">false</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="hibernate.cache.use_structured_entries">false</property>
        <property name="hibernate.cache.use_second_level_cache">false</property>

        <mapping class="com.basic.Owner" />
        <mapping class="com.basic.Pet" />

    </session-factory>
</hibernate-configuration>
As can be seen all the database settings are in provider specific format i.e. Hibernate style. This completes the code setup. Now to run the code.

6 comments:

  1. The Owner class references the Pet Class and the Pet Class references the Owner classes. In regards to a circular dependency, is that a good thing?

    ReplyDelete
    Replies
    1. I would call it a bi-directional relation and not a circular dependency. It often is the case that you need the ability to navigate from the pet to the owner. The set of pets in the owner class allows for easy cascade operations. If bi-directionality is not your requirement, then you can always avoid the same. There is no negative impact as such if you do or do not keep it.

      Delete
    2. I would be careful with that. You cannot create and compile Owner.java by itself. It will complain there is no class called Pet. The same for Pet.java which will complain about no class called Owner. This only to compile this is to save and compile them together or use inner classes, etc. The classes cannot exist separately and alone. Good articles by the way on JPA.
      http://en.wikipedia.org/wiki/Layer_%28object-oriented_design%29
      http://en.wikipedia.org/wiki/Association_%28object-oriented_programming%29
      http://en.wikipedia.org/wiki/Object_composition#Aggregation

      Delete
    3. If you are worried that the reference in both files will prevent compilation, then that is not the case - check http://learningviacode.blogspot.com/2013/03/my-first-jpa-application-3.html

      Delete
  2. You are using both
    @Entity annotations, AND
    in your xml-config: (or com.basic.Owner)

    You dont need both -- either annotations OR xml-config should be enough.

    ReplyDelete
    Replies
    1. My comment got corrupted because of angle-brackets, but I hope the meaning is clear :)
      You dont need to specify class-names of entities in xml, when using annotations.

      Delete