Engineering Full Stack Apps with Java and JavaScript
Hibernate uses some strategies for saving an inheritance hierarchy of classes created in Java to the databases like SINGLE_TABLE, TABLE_PER_CLASS and JOINED. We use @Inheritance annotation over the entity class to specify the inheritance strategy. We specify the inheritance strategy by assigning a strategy from the InheritanceType enumeration to the strategy parameter of the @Inheritance annotation. Strategies defined in the InheritanceType enumeration are InheritanceType.SINGLE_TABLE, InheritanceType.TABLE_PER_CLASS and InheritanceType.JOINED. If you don’t provide the @Inheritance annotation or any strategies, the default strategy is SINGLE_TABLE.
In the TABLE_PER_CLASS strategy, hibernate will create a separate table for all the classes in an inheritance hierarchy. All the columns that you receive as part of inheritance will be part of the final class. This is better than single table strategy that you will only have columns of your class. However it is still not normalized completely as inherited columns are repeated in all tables. For instance, Shape fields such as id and fillcolor are repeated in all the tables. We will understand this better with an example.
In this example, we will create and save three entity classes – Shape, Rectangle and Circle; where Rectangle and Circle extends from Shape. If you are not following the tutorials in order, refer to the article http://javajee.com/your-first-hibernate-program for the basic setup including hibernate configuration file. You should replace the user class in the configuration file with Shape, Rectangle and Circle classes as:
<!-- Names the annotated entity class -->
<mapping class="com.javajee.hiberex.dto.Shape"/>
<mapping class="com.javajee.hiberex.dto.Rectangle"/>
<mapping class="com.javajee.hiberex.dto.Circle"/>
Else you will get org.hibernate.MappingException: Unknown entity:…
The base entity class, shape will have id and fillcolor properties:
@Entity
@Inheritance (strategy = InheritanceType.TABLE_PER_CLASS)
public class Shape {
@Id @GeneratedValue(strategy=GenerationType.TABLE)
int shapeId;
String fillcolor;
//Getters and setters not shown here; create them manually or generate in eclipse.
}
Since I am using MySQL database, I am using @GeneratedValue(strategy=GenerationType.TABLE) for TABLE_PER_CLASS strategy. With other databases, just @GeneratedValue as in SINGLE_TABLE strategy might just work. Scroll down to the heading '@GeneratedValue annotation considerations in TABLE_PER_CLASS strategy' for more details.
Entity class Rectangle extends from Shape, and have length and breadth properties:
@Entity
public class Rectangle extends Shape{
long length;
long breadth;
//Getters and setters not shown here; create them manually or generate in eclipse.
}
This class is same as in SINGLE_TABLE strategy without @DiscriminatorValue annotation.
Entity class Circle extends from Shape, and has one property radius:
@Entity
public class Circle extends Shape{
long radius;
//Getters and setters not shown here; create them manually or generate in eclipse.
}
This class is same as in SINGLE_TABLE strategy without @DiscriminatorValue annotation.
Now use the below test class to create objects for these classes, populate data and save them:
public class ShapeTest {
public static void main(String[] args) {
Shape s = new Shape();
s.setFillcolor("Green");
Rectangle r = new Rectangle();
r.setBreadth(4);
r.setLength(6);
Circle c = new Circle();
c.setRadius(5);
Configuration configuration = new Configuration();
configuration.configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties()).buildServiceRegistry();
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(s);
session.save(r);
session.save(c);
session.getTransaction().commit();
}
}
This class is same as in SINGLE_TABLE strategy.
When you execute the above test class, below queries are executed by hibernate (as seen from the console):
Hibernate: insert into Shape (fillcolor, shapeId) values (?, ?)
Hibernate: insert into Rectangle (fillcolor, breadth, length, shapeId) values (?, ?, ?, ?)
Hibernate: insert into Circle (fillcolor, radius, shapeId) values (?, ?, ?)
You can see that hibernate has created one table each for Shape, Rectangle and Circle. Also, Shape’s fillcolor and shapeId, which are inherited by all, appears in all the tables. Things will be more clear, once you see the database snapshot. Just do a select all query (select * from Shape/Rectangle/Circle). This is better than SINGLE_TABLE as it eliminated irrelevant columns with NULL values, but this can still be normalized using joins.
@GeneratedValue is used for primary key generation. We can specify a strategy for primary key generation using the enumeration GenerationType. GenerationType defines the types of primary key generation and can have values AUTO, IDENTITY, SEQUENCE and TABLE. AUTO indicates that the persistence provider should pick an appropriate strategy for the particular database. IDENTITY indicates that the persistence provider must assign primary keys for the entity using database identity column. SEQUENCE indicates that the persistence provider must assign primary keys for the entity using database sequence column. TABLE indicates that the persistence provider must assign primary keys for the entity using an underlying database table to ensure uniqueness.
Usually @GeneratedValue ,which is same as @GeneratedValue( strategy = GenerationType.AUTO), can be used for any id generation. However there might be some exceptions or limitations for some jpa provider with some databases with respect to the default behavior. In that case, it is better to use a specific type than AUTO. For instance, In table per class strategy, if you use AUTO with MySQL when using hibernate3, you will get an exception org.hibernate.MappingException: Cannot use identity column key generation with <union-subclass> mapping for:… You can resolve this by using GenerationType.TABLE as:
@Id @GeneratedValue(strategy=GenerationType.TABLE)
int shapeId;
If the database that you use has problems with AUTO, try using TABLE for TABLE_PER_STRATEGY. Else just play with GenerationType and find the right one. If you find something useful, please add it here in the comments and help others.
@DiscriminatorValue and @DiscriminatorColumn annotations should be used with SINGLE_TABLE strategy as discriminator column is only present in SINGLE_TABLE strategy. There is no special use if you use them with other strategies, however you will not see any error if you still have them (tested with hibernate3).