Hibernate essentials

Hibernate

  • Configuration
  • – via hibernate.cfg.xml (or hibernate.propeties) and *.hbm.xml mapping files
    – via annotations

  • single thread-safe instance of SessionFactory which is used by many thread to create Session instances.
  • SessionFactory is a concept of single datastore, is build usually once at startup
  • Session is lightweight, non-thread-safe, cannot be shared among threads, it represents a signle unit of work with database, it is opened by SessionFactory and closed when all the work is complete. Session obtains db connection lazily – only when required. In order not to create too many sessions, for the same thread the same calls to get currentSession return the same session instance via ThreadLocal mechanism. Session is a primary interface to persistence service. It is important to close the session after your work is done.
  • transaction – short lived, single threaded object used by application to specify atomicity, transaction abstracts your application code from underlying JDBC, JTA transactions. Sometimes for long-user-thinking transaction, it is worth to split one long transaction into several ones (by calling tx.commit()). You can use detached objects to from first transaction to transfer data up to presentation layer and later reattach them to new transaction.
  • Object states

  • persistent – short lived objects that are currently associated with a session (signle threaded), they store persistent state which is synchronized with db (depending on your flush strategy – e.g. auto-flush immediately on call setXXX or remove from collection, or explicit synchronization on session.flush(), transaction.commit()). When the session is closed persistent objects become detached objects. Persistent objects: car after session.save(car) or car2 after session.load(Car.class)
  • detached – are not currently associated with the session, they can be used as Data Transfer Objects without having impact on database so they can be passed across all the layers. Detached objects can be attached to session by calling session.update() or session.saveOrUpdate() and become persistent objects. Detached objects: car after session.evict(car)
  • transient – newly created objects, were never associated with a session, they can be freely used as Data Transfer Object without having impact on db, they become persistent after calling session.save(). Transient objects car after car = new Car(); or car2 after session.delete(car2)
  • Detached objects (that were associated with a session) and transient objects (newly created) are distinguishable by version property – if there is one – otherwise by identifier value (work only for Hibernate managed surrogate key).

    Hibernate Query Language HQL is a OO extension to SQL supporting inheritance and polymorphism. Is uses classes and properties instead tables and columns, HQL is less verbose and supports automatic association joining:

    Query query = session.createQuerry("Select car from Car as car join car.parts as part where car.color = 'black' and part.cost > '100'");
    return query.list();

    SQL verbose: Car as car join Part part on car.part_id = part.id

    Query by criteria:
    Criteria criteria = session.createCriteria(Car.class)
    criteria.add(Expression.eq("color", "black"));
    return criteria.list();

    To support legacy applications that have null values e.g. for Boolean in legacy table, it is recommended to use wrapper classes (Integer, Boolean) instead of primitives, so that Hibernate will not throw PropertyAccessException.

    Graph of objects manipulation

  • cascade – transitive persistence – whether if parent is deleted should also all children be deleted (all-delete-orphan), whether if updates/deletes/saves are automatically passed to children (save-update, delete or all), whether if delete a child if gets disconnected from parent (delete-orphan)
  • lazy loading – default true – determines if all associated objects should be loaded lazily or eagerly when calling session.get(), session.load() or session.find(). The lazy loading uses proxy objects. If you want to access lazily loaded collection, make sure session is still open, otherwise the exception will be thrown.
  • When object is not found session.load() throws exception, session.get() returns null.

    Example:

    @MappedSuperclass
    public class AbstractDomainObject implements Serializable {

      /** Unique identifier */
      private Long id;

      /** version for optimistic locking */
      private transient int version = -1;

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

      @Version
      @Column(name = "VERSION", nullable = false)
      public int getVersion() {
        return version;
      }

        ... setters, hashcode and equals ...
    }

    @Entity
    @Table(name = "COMPANY")
    public class Company extends AbstractDomainObject {

      private String name;
      private List employees = new ArrayList();

      // hibernate required
      Company() {
      }

      // we must explicitly set ref from empl to its company
      public Company(String name, List employees) {
        this.name = name;
        for (Employee employee : employees) {
          employee.setCompany(this);
          this.employees.add(employee);
        }
      }

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

      @OneToMany(mappedBy="company",cascade=ALL,fetch=EAGER)
      public List getEmployees() {
        return employees;
      }

        ... setters ...

    }

    SQL:
    create table COMPANY (ID bigint not null auto_increment, VERSION integer not null, NAME varchar(255), primary key (ID))

    insert into COMPANY (VERSION, NAME) values (0, 'K')

    @Entity
    @Table(name = "EMPLOYEE")
    public class Employee extends AbstractDomainObject {

      private String name;
      private Company company;

      // Hibernate required
      Employee() {
      }


      public Employee(String name) {
        this.name = name;
      }

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

      @ManyToOne()
      @JoinColumn(name = "COMPANY_ID", nullable = true)
      @ForeignKey(name = "FK_EMPLOYEE_COMPANY")
      public Company getCompany() {
        return company;
      }

        ... setters ...

    }

    SQL:
    create table EMPLOYEE (ID bigint not null auto_increment, VERSION integer not null, NAME varchar(255), COMPANY_ID bigint, primary key (ID))

    alter table EMPLOYEE add index FK_EMPLOYEE_COMPANY (COMPANY_ID), add constraint FK_EMPLOYEE_COMPANY foreign key (COMPANY_ID) references COMPANY (ID)

    insert into EMPLOYEE (VERSION, COMPANY_ID, NAME) values (0, 1, 'aaa')

    Company has employees. Presence of foreign key on EMPLOYEE.COMPANY_ID referencing COMPANY.ID (primary key) means that Employee only exists when is associated with already existing Company.

    TEST

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath:test-resources.xml", "classpath:infrastructure-context.xml",
    "classpath:datasources.xml", "classpath:dao-context.xml", "classpath:transaction-context.xml",
        "classpath:hibernate-annotated-classes-context.xml" })
    public class HibernatePersonDaoTest {

      @Autowired
      private CompanyDao dao;

      @Test
      public void testSaveIntoDB() {
        Employee a = new Employee("aaa");
        Employee b = new Employee("bbb");
        Employee c = new Employee("ccc");
        dao.saveOrUpdate(new Company("K", asList(a, b, c)));

        Employee x = new Employee("xxx");
        Employee y = new Employee("yyy");
        Employee z = new Employee("zzz");
        dao.saveOrUpdate(new Company("L", asList(x, y, z)));

        System.err.println("---------------");
        List companies = companyDao.getCompanies();
        for (Company company : companies) {
          System.err.println(company.getName());
        }
      }
    }

    For EAGER (retrieves company and all associated employees even though employees are not needed/accessed at the moment: 1 + 2 selects) :
    —————
    select company0_.ID as ID1_, company0_.VERSION as VERSION1_, company0_.NAME as NAME1_ from COMPANY company0

    select employees0_.COMPANY_ID as COMPANY4_1_, employees0_.ID as ID1_, employees0_.ID as ID0_0_, employees0_.VERSION as VERSION0_0_, employees0_.COMPANY_ID as COMPANY4_0_0_, employees0_.NAME as NAME0_0_ from EMPLOYEE employees0_ where employees0_.COMPANY_ID=2

    select employees0_.COMPANY_ID as COMPANY4_1_, employees0_.ID as ID1_, employees0_.ID as ID0_0_, employees0_.VERSION as VERSION0_0_, employees0_.COMPANY_ID as COMPANY4_0_0_, employees0_.NAME as NAME0_0_ from EMPLOYEE employees0_ where employees0_.COMPANY_ID=1
    K
    L
    —————
    For LAZY (retrieve only company: 1 select)
    select company0_.ID as ID1_, company0_.VERSION as VERSION1_, company0_.NAME as NAME1_ from COMPANY company0_
    K
    L

    Best practices

    A Hibernate persistent class is a POJO, which has not interface to be implemented and no persistent class to be extended. You need a default no-argument constructor with setter and getter methods. The equals() and hashCode() methods should be implemented based on your business key (not Hibernate managed surrogate key id which is set after persisting). It is recommended to implement serializable interface to enable future migration around multi-processor cluster. Persistent class shouldn’t be final to exabling proxy for lazy loading.

    Database identity: if both records have the same id as primary key, then they are referring to the same row in the database.
    Object identity: memory comparison (==), equality is a.equals(b).

    Persistent objects defined by Hibernate

  • Entity objects – objects with identity i.e. Hibernate managed id, e.g. Customer
  • Value objects – don’t have identity and exist only in relationship with entity objects, e.g. Address
  • Mapping inheritance in Hibernate

  • one table for whole hierarchy – each row can hold an object of any type in the hierarchy, needed extra column for “discriminator” property to tell the type of object in the hierarchy
  • table per subclass – every class/subclass (even abstract) that has persistent properties has its own table. Each subclass table has a primary key as foreign key to the superclass table
  • table per concrete class – table for each subclass (concrete, non-abstract), all properties, including inherited, are mapped to one table.
  • First and second level chage
    Hibernate uses two different caches for objects: first-level cache and second-level cache. First-level cache is associated with the Session object, while second-level cache is associated with the Session Factory object. By default, Hibernate uses first-level cache on a per-transaction basis. Hibernate uses this cache mainly to reduce the number of SQL queries it needs to generate within a given transaction. For example, if an object is modified several times within the same transaction, Hibernate will generate only one SQL UPDATE statement at the end of the transaction, containing all the modifications. This article focuses on second-level cache. To reduce database traffic, second-level cache keeps loaded objects at the Session Factory level between transactions. These objects are available to the whole application, not just to the user running the query. This way, each time a query returns an object that is already loaded in the cache, one or more database transactions potentially are avoided.

    Data transfer object (DTO), formerly known as value objects or VO are used to transfer data between software application subsystems. DTO does not have any behaviour except for storage and retrieval of its own data (accessors and mutators).

    Advertisements

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s