There are scenarios that an application needs to access multiple databases with different table names but with the same table structures. One way is to create separate entities, repositories for each connection. But it causes code duplication, and in the end, the code becomes difficult to maintain. Another approach is to override table names at the runtime in Hibernate. In this article, we discuss how to change the @Table name value on runtime in Hibernate.
Limitations
Before jumping to the implementation, it’s crucial to know the limitation. Table names can be changed/overridden only at the application start-up time. Not while the application is running. Maybe there’s a way to achieve that, but Hibernate doesn’t support it.
Additionally, the assumption is that you use either Hibernate directly or via Spring/Spring Boot Data JPA. It should work in both cases.
Override Entity @Table
names
First, we must create an XML file under the resources
directory to provide alternative table names. Let’s call it orm.xml
.
<entity-mappings version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd">
<!-- The new table name -->
<entity class="com.madadipouya.example.multiple.datasource.song.entity">
<table name="songs_archive"></table>
</entity>
</entity-mappings>
Then we create a data source configuration file and load the orm.xml
file based on a condition (profile name in this case). For that, create a config file as below,
package com.madadipouya.example.multiple.datasource.song.config
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@EnableJpaRepositories(entityManagerFactoryRef = "songEntityManagerFactory",
transactionManagerRef = "songTransactionManager",
basePackages = {"com.madadipouya.example.multiple.datasource.song"})
public class SongDatasourceConfig {
private final Environment environment;
public SongDatasourceConfig(Environment environment) {
this.environment = environment;
}
@Bean
@Primary
public DataSource songDatasource() {
return return DataSourceBuilder.create()
.url("jdbc:mysql://localhost:3306/ds1?useUnicode=yes&characterEncoding=UTF-8&useLegacyDatetimeCode=false&serverTimezone=UTC")
.username("root")
.password("secret")
.build();
}
@Bean("songTransactionManager")
@Primary
public PlatformTransactionManager songTransactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
@Bean("songEntityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean songEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(this.songDatasource());
entityManager.setPackagesToScan("com.madadipouya.example.multiple.datasource.song");
entityManager.setPersistenceUnitName("songDatasource");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
entityManager.setJpaVendorAdapter(vendorAdapter);
if (isArchiveProfile()) {
entityManager.setMappingResources("orm.xml");
}
return entityManager;
}
private boolean isArchiveProfile() {
return environment.acceptsProfiles(Profiles.of("archive"));
}
}
The values of JDBC URL, username, and password can be loaded from the application.yml
file. You can learn more about it here.
If you have more than one mapping, create multiple .xml
files and put them in the resources
directory. However, with a single database connection, you can only load one of them at a time.
Although the criteria to load orm.xml
is based on the profile, you can change that condition according to your needs.
As we have stated earlier, to change the @Table name value/mapping on runtime in Hibernate, you must restart the application.
Inline/featured images credits
- Featured image by Elchinator from Pixabay