Saturday, January 12, 2019

How to change the table name of an Hibernate entity at run-time?


Sometimes, There are the tables that are being generated on a monthly basis but the table structure of all monthly tables are same.

Let's say we have a biometric fingerprint attendance machine for our employees and this machine is automatically storing attendance details in the database on monthly basis. Hence the table name changes every month but its column/structure remains the same.

Example:- Suppose today is 13-01-2019. Now for today's data will be stored into table ATTENDANCE_1_2019.
Similarly, our tables name will be ATTENDANCE_2_2019, ATTENDANCE_3_2019 ...... ATTENDANCE_12_2019.

But the columns of all the tables remains the same.
deviceLogId,
empId,
checkin
checkout

Now, At end of the month, we need to calculate the salary for every employee. For this, we need to read the data from these tables. For this, we are using Hibernate. As we know Hibernate is an ORM Framework. Means for a per table we need to create Entity class so that Hibernate can map the table into the entity class and vice-versa.

So, Let's create our entity class.

@Entity
@Table(name = "ATTENDANCE_1_2019")
public class Attendance {

 @Id
 private int sno;
 private int deviceLogId;
 private int employeeId;
 private Date checkin;
 private Date checkout;

//setters & getters
}

Now we will write our DAO class to fetch to fetch the data from table ATTENDANCE_1_2019. It will work fine but what happens if month comes to February?

In February month our Biometric fingerprint attendance machine stores attendance details in ATTENDANCE_2_2019 table. Then how can we fetch the data from ATTENDANCE_2_2019(February) table because our Attendance entity class is mapped with ATTENDANCE_1_2019 table?

Hence again we need to write entity class for February(ATTENDANCE_2_2019), March(ATTENDANCE_3_2019), April(ATTENDANCE_4_2019) and so on. Now the question is how many entity class you will write? In a year you need to write 12 entity class. So is it a good approach to write too many entity class even all the entity class has the same structure/attributes just because of table name?

Hence we can solve this problem by changing the Entity class name at runtime by using Hibernate Interceptor.

The Hibernate Interceptor is an interface that allows us to react to certain events within Hibernate. Interceptor Interface provides methods, which can be called at different stages to perform some required tasks like onSave(), onDelete(), onPrepareStatement() etc.
You can read more in Hibernate Documentation.


Hence we will take advantage of onPrepareStatement(String sql) method. As we Hibernate uses HQL(Hibernate Query Language) but internally these query is converted into SQL. Means at the time of generating SQL if there is any callback method where we can get the SQL and can change the table name then we can solve this problem. Hence Hibernate has provided one method called onPrepareStatement(). Before sending the query to the Database first it will call onPrepareStatement(String sql). Hence here we can change the table name by replacing the older name with the new table name.

Example:- So currently, we have the Attendance list of January & February month. We will fetch this data using Hibernate.
Attendance_1_2019


Attendance_2_2019

For this, we need to create one DAO like this
@Repository
public class AttendanceDAO {
 @Autowired
 private SessionFactory sessionFactory;

 public List getAllAttendance() {
  Session session = sessionFactory.openSession();
  Query query = session.createQuery("from Attendance");
  List attendanceList = query.list();
  return attendanceList;
 }
}
As we are using show_sql=true, we can see the query generated by the Hibernate.
Hibernate:
    select
        attendance0_.sno as sno1_0_,
        attendance0_.checkin as checkin2_0_,
        attendance0_.checkout as checkout3_0_,
        attendance0_.deviceLogId as deviceLo4_0_,
        attendance0_.empId as empId5_0_
    from
        ATTENDANCE_1_2019 attendance0_
But here the problem is, This session will always go to ATTENDANCE_1_2019 table because in entity class it is mapped to this table. Hence we need to write our custom Interceptor class like this
public class CustomInterceptor extends EmptyInterceptor {
 @Override
 public String onPrepareStatement(String sql) {
  System.err.println("Before Modifying SQL =" + sql);
  sql = sql.replace("ATTENDANCE_1_2019 ", "ATTENDANCE_2_2019 ");
  System.err.println("After Modifying SQL =" + sql);
  return sql;
 }
}
And in this class, we are modifying the existing query by replacing the table name and again we are sending this query to the HIbernate. Now Hibernate will fetch the data from ATTENDANCE_2_2019 table.

Here I have hardcoded the table name which we should not. Hence we can use the Calendar class of Java to get the current month and year. Hence we can change your CustomInterceptor like this
public class CustomInterceptor extends EmptyInterceptor {
 @Override
 public String onPrepareStatement(String sql) {
  System.err.println("Before Modifying SQL =" + sql);
  Calendar calendar = Calendar.getInstance();
  int month = calendar.get(Calendar.MONTH) + 1;
  int year = calendar.get(Calendar.YEAR);
  String tableName = "ATTENDANCE_" + month + "_" + year;
  sql = sql.replace("ATTENDANCE_1_2019 ", tableName);
  System.err.println("After Modifying SQL =" + sql);
  return sql;
 }
}


In this way, we can change the entity class table at runtime. No need to write multiple entity class. One entity class is enough.

Github Link : ChangeTableNameAtRuntime


Hope we understood. If still any doubts you can comment here.


Share this

15 Responses to "How to change the table name of an Hibernate entity at run-time?"

  1. Awesome explanation...Heads off sir

    ReplyDelete
  2. Is there any way to get table name from sql String passed as argument into this function?
    As you mentioned we can replace source and destination table. I want both actual and replacement string dynamically. Here i am able to get replacement string dynamically but not able to find way to get table name means actual string for ReplaceAll function.

    ReplyDelete
  3. You have saved a lot of my time! Thank you very much!

    ReplyDelete
    Replies
    1. Author has used deprecated onPrepearedStatement method.
      https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/Interceptor
      For Hibernate 5+ use inspect() method:
      https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/resource/jdbc/spi/StatementInspector.html

      Delete
  4. how to perform CRUD operations on those tables, I mean how to use table names for writing queries in repository?

    ReplyDelete
  5. Replies
    1. Kindly push your code into Github and share the link. We will look into it.

      Delete
  6. Does JPA has something similar?

    ReplyDelete
  7. Excellent article and with lots of information. I really learned a lot here. Do share more like this.
    Blue Prism Vs Uipath
    Uipath Automation

    ReplyDelete
  8. excellent article,,i need to change table name at runtime but my db was postgrel and am using jparepository for crud operations,,what should i change in the code

    ReplyDelete