How to address Schema Migration for Dummies

  1. Be very clear in what your PersistentEntity (an entity that directly corresponds to a db table) is and separate it from your Business related entities that may or may not have one to one relationship with the tables. Ideally, Business Entities should be defined on a need basis, however PersistentEntities should be defined as soon as your schema / data model design is ready for the first iteration of development.
  2. Once you create entities, you will annotate these entities, and I haved shared an example of such entities. These annotations should follow JPA annotations standard and not Hibernate annotations, because that way you will avoid vendor lockin, and be following a standard. JPA is a standard developed by Java Community and has JSR (Java Specification Request) which is implemented by Hibernate, J2EE, TopLink or any other middle-tier OR implementation.
  3. Add Hibernate Synchronizer plugin to Eclipse (you can also use the schem exporter command line utility, which you will find docs on RedHat Hibernate website). I highly recommend using Eclipse plugin because it makes the process visual and easy to follow.
  4. Use Hibernate Sychronizer to Export the Schema based on the PersistentEntities, this will require you to configure your persistence.xml file, which is usually placed in the class path, inside a jar or directly under the class path  i can share some examples for demonstration.
  5. The schema exporter works by pointing to your data base instance and generating all the tables that you have defined as PersistentEntities, taking care of all the constraint definitions, indexes etc. Will share a sample of that too.
  6. What you now have is the v1 of your db. This is ready to be used in your application by defining DAO or Data Access Layer, which abstracts out the operations performed on PersistentEntities. Note: As long as you have a good abstraction around DAO, your persistent entities will mostly remain unchanged until you decide to add more columns or tables to your schema.
  7. Now for v2, if you are adding more tables and columns, you will start with modifying the PersistentEntities directly and adding news ones where required. Reminder: All the entity relationships are also defined using Annotations, as you already are aware, and hence adding new tables only requires some new relationship annotations to be added to exisinting entities if they are going to have a new relationship with the newly added entity.
  8. After you are happy with your v2 db schema design and PersistentEntities created thereof, you will point to your Development DB server and generated the new schema based db instance.
  9. In this step you will need to have a tool that can compare db instance v1 with db instance v2 and generate a migration script for the DDL part. For data that needs to be migrated, you will have to write your own migration scripts. Note: If you are using MySQL then you are lucky because MySQL Workbench has great tools for schema comparison and migration DDL auto generation.

PersistentEntity

package com.rishik.hibernate.entity;

import java.io.Serializable;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;

/**
* This is an object that contains data related to the file table.
*
*
* table="file"
*/

@Entity
@Table(name="file")
@SuppressWarnings("serial")
public class FileEntity implements Serializable {

public static String REF = "FileEntity";
public static String PROP_TYPE = "type";
public static String PROP_ID = "id";
public static String PROP_USER = "user";
public static String PROP_CONTENT = "content";
public static String PROP_WORKSPACE = "workspace";
public static String PROP_CREATED_DATE = "createdDate";
public static String PROP_LAST_EDITED_DATE = "lastModifiedDate";
public static String PROP_LAST_EDITED_BY = "lastModifiedBy";

// constructors
public FileEntity () {
initialize();
}

/**
* Constructor for primary key
*/
public FileEntity (java.lang.Long id) {
this.setId(id);
initialize();
}

/**
* Constructor for required fields
*/
public FileEntity (
java.lang.Long id,
java.lang.Long userId,
java.lang.Long workspaceId) {

this.setId(id);
this.setUser(user);
this.setWorkspace(workspace);
initialize();
}

protected void initialize () {}

private int hashCode = Integer.MIN_VALUE;

// primary key
private java.lang.Long id = 0L;

// fields
private UserEntity user;
private WorkspaceEntity workspace;
private java.lang.String content;
private java.lang.String type;
private java.util.Date createdDate;
private java.util.Date lastModifiedDate;
private UserEntity lastModifiedBy;

/**
* Return the unique identifier of this class
* @hibernate.id
* generator-class="native"
* column="file_id"
*/
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Generated(GenerationTime.INSERT)
@Column(name="file_id",insertable=true,updatable=false)
public java.lang.Long getId () {
return id;
}

/**
* Set the unique identifier of this class
* @param id the new ID
*/
public void setId (java.lang.Long id) {
this.id = id;
this.hashCode = Integer.MIN_VALUE;
}

/**
* Return the value associated with the column: user_id
*/
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="user_id")
public UserEntity getUser () {
return user;
}

/**
* Set the value related to the column: user_id
* @param user the user_id value
*/
public void setUser (UserEntity user) {
this.user = user;
}

/**
* @return
*/
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="workspace_id")
public WorkspaceEntity getWorkspace() {
return workspace;
}

/**
* @param workspace
*/
public void setWorkspace(WorkspaceEntity workspace) {
this.workspace = workspace;
}

/**
* Return the value associated with the column: content
*/
@Column(name="content")
@Lob
public java.lang.String getContent () {
return content;
}

/**
* Set the value related to the column: content
* @param content the content value
*/
public void setContent (java.lang.String content) {
this.content = content;
}

/**
* Return the value associated with the column: type
*/
@Column(name="type")
public java.lang.String getType () {
return type;
}

/**
* Set the value related to the column: type
* @param type the type value
*/
public void setType (java.lang.String type) {
this.type = type;
}

/**
* @return the createdDate
*/
@Column(name="created_date")
public java.util.Date getCreatedDate() {
return createdDate;
}

/**
* @param createdDate the createdDate to set
*/
public void setCreatedDate(java.util.Date createdDate) {
this.createdDate = createdDate;
}

/**
* @return the lastModifiedDate
*/
@Column(name="last_modified_date")
public java.util.Date getLastModifiedDate() {
return lastModifiedDate;
}

/**
* @param lastModifiedDate the lastModifiedDate to set
*/
public void setLastModifiedDate(java.util.Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}

/**
* @return the lastModifiedBy
*/
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="last_modified_by")
public UserEntity getLastModifiedBy() {
return lastModifiedBy;
}

/**
* @param lastModifiedBy the lastModifiedBy to set
*/
public void setLastModifiedBy(UserEntity lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}

public boolean equals (Object obj) {
if (null == obj) return false;
if (!(obj instanceof FileEntity)) return false;
else {
FileEntity pOFile = (FileEntity) obj;
if (null == this.getId() || null == pOFile.getId()) return false;
else return (this.getId().equals(pOFile.getId()));
}
}

public int hashCode () {
if (Integer.MIN_VALUE == this.hashCode) {
if (null == this.getId()) return super.hashCode();
else {
String hashStr = this.getClass().getName() + ":" + this.getId().hashCode();
this.hashCode = hashStr.hashCode();
}
}
return this.hashCode;
}

public String toString () {
return super.toString();
}

}