Hibernate naming strategy per entity - java

I have one global naming strategy but for a few entities I want to use a different one. Is it possible in jpa or hibernate?
clarification: i don't want to use #Table(name="xxx") nor #Column(name="xxx"). i'm asking about naming strategy component (described for example here: Hibernate naming strategy). that's a component that infer the column and table names for you

I don't see a way in the Hibernate source code. The EntityBinder is coming up with names using ObjectNameNormalizer.NamingStrategyHelper, which gets the naming strategy from either Configuration.namingStrategy (the global one) or from a complex path which goes through MetadataImpl and lands nowhere (no usages).
So you're likely stuck with overriding field names manually. I don't even see an obvious way to get context about the field, so I think even a split-brain naming strategy looks like it's out of the question.
Update: After seeing #anthony-accioly's answer, I thought I that last sentence may have been wrong. So I tested it as follows
package internal.sandbox.domain;
#Entity
public class SomeEntity {
private String id;
private String someField;
#Id
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSomeField() {
return someField;
}
public void setSomeField(String someField) {
this.someField = someField;
}
}
with a JpaConfiguration as follows
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("internal.sandbox.dao")
#Import(DataSourceConfiguration.class)
public class JpaConfiguration {
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean(DataSource dataSource) {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQL82Dialect");
vendorAdapter.setDatabase(Database.POSTGRESQL);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("internal.sandbox"); // note, no ".domain"
factory.setDataSource(dataSource);
Properties properties = new Properties();
properties.setProperty("hibernate.cache.use_second_level_cache", "false");
properties.setProperty("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
factory.setJpaProperties(properties);
return factory;
}
...
a Spring Data DAO as follows
public interface SomeEntityDao extends CrudRepository<SomeEntity, String> {
}
and an integration test as follows
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ApplicationConfiguration.class, JpaConfiguration.class})
public class SomeEntityDaoIntegrationTests {
#Autowired
private SomeEntityDao someEntityDao;
#Test
public void testSave() {
SomeEntity someEntity = new SomeEntity();
someEntity.setId("foo");
someEntity.setSomeField("bar");
this.someEntityDao.save(someEntity);
}
}
I put breakpoints in the ImprovedNamingStrategy, and classToTableName() was called with "SomeEntity" and propertyToColumnName() was called with "someField".
In other words, package information isn't being passed in, so at least in this setup, it can't be used to apply a different naming strategy based on package name.

Related

Repositories with native queries fail in test environment - postgres, jpa, spring

I have set up integration tests for a spring boot project using test containers (sets up a docker instance with postgresql). The tests work great if the repositories that I am testing against do not use native queries. However, whenever a repository contains a native query I get the following error: ERROR: relation "my_table_here" does not exist. How do I get my test configuration to work to allow native queries?
Below is my test set up:
#RunWith(SpringRunner.class)
public class TestPostgresql {
#ClassRule
public static PostgreSQLContainer postgreSQLContainer = PostgresDbContainer.getInstance();
/**
* ************ REPOSITORIES ************
*/
#Autowired
NativeQueryRepository nativeQueryRepository;
#TestConfiguration
#EnableJpaAuditing
#EnableJpaRepositories(
basePackageClasses = {
NativeQueryRepository.class
})
#ComponentScan(
basePackages = {
"com.company.project.package.repository"
}
)
static class PostgresConfiguration {
/**
* ************ DATABASE SETUP ************
*/
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(postgreSQLContainer.getJdbcUrl());
dataSource.setUsername(postgreSQLContainer.getUsername());
dataSource.setPassword(postgreSQLContainer.getPassword());
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new JpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.company.project");
factory.setDataSource(dataSource());
return factory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
}
EDIT: I believe this has something to do with the naming strategy?
For greater context here is an example of how the nativeQuery is used in the repository
#Repository
public interface NativeQueryRepository extends JpaRepository<NativeEvent, Long> {
#Modifying
#Transactional
#Query(value = "UPDATE native_event SET state = :state " +
"WHERE secondary_id = :secondaryId", nativeQuery = true)
void updateState(
#Param("state") String state,
#Param("secondaryId") String secondaryId);
}
I also tried update the testProperties on the static class inside TestPostgresql by adding the annotation:
#TestPropertySource(properties = {
"spring.jpa.hibernate.naming-strategy=org.springframework.boot.orm.jpa.SpringNamingStrategy"
})
However, with no change to the error received.
EDIT: add NativeEvent:
#Entity
#Table(
name = "NativeEvent",
indexes = {
#Index(name = "idx_native_event_secondary_id", columnList = "secondaryId")
}
)
#EntityListeners(AuditingEntityListener.class)
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class NativeEvent implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="secondaryId", nullable=false)
private String secondaryId;
#Column(name="state")
private String state;
}
You are doing manual configuration instead of using the runtime configuration. Hence different treatment of naming strategies. Instead you should be reusing the same configuration instead of writing your own.
Either use an #SpringBootTest or #DataJpaTest and only re-configure the DataSource.
Do something with an ApplicationContextInitializer to get the JDBC properties into the ApplicationContext.
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(initializers = {TestPostgresql.JdbcInitializer.class})
public class TestPostgresql {
#ClassRule
public static PostgreSQLContainer postgreSQLContainer = PostgresDbContainer.getInstance();
/**
* ************ REPOSITORIES ************
*/
#Autowired
NativeQueryRepository nativeQueryRepository;
static class JdbcInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
"spring.datasource.username=" + postgreSQLContainer.getUsername(),
"spring.datasource.password=" + postgreSQLContainer.getPassword()
).applyTo(configurableApplicationContext.getEnvironment());
}
}
}
This will reuse the configuration from the runtime in your test. Instead of #SpringBootTest you should als be able to use #DataJpaTest(NativeQueryRepository.class) to make a sliced test for JPA only.
You assign your table name explicitly like this:
#Table(name = "NativeEvent")
but in your native query you have a different name for that table:
#Query(value = "UPDATE native_event ...)
Either remove the name attribute from your #Table annotations (assuming your naming strategy will produce names like native_event) or change table name in native query to be nativeevent or nativeEvent so in this case just remove the underscore.
Somewhat related post

Spring Boot Autowiring From Another Module

I am trying to establish connection between 3 modules in my project. When I try to reach my object with #Autowired error shows up. I'll explain my scenario a little bit.
MODULES
All of these modules have been connected inside of pom.xml. Lets talk about my problem.
C -> ROUTE.JAVA
.
.
.
#Autowired
public CommuniticationRepository;
#Autowired
public Core core;
.
.
.
B -> CORE
public class Core {
private int id;
private String name;
private Date date;
public Core(int id, String name, Date date) {
this.id = id;
this.name = name;
this.date = date;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
ERROR
Field communicationRepositoryin com.demo.xyz.A.RestControllers.Route required
a bean of type 'com.demo.xyz.A.CommunicationRepository' that could not be
found.
Action:
Consider defining a bean of type 'com.demo.xyz.A.CommunicationRepository' in
your configuration.
A - > REPOSITORY.JAVA
#Component
#Repository
public interface CommunicationRepository extends CrudRepository<Communication, Date> {
List<Communication> findByDate(Date date);
void countByDate(Date date);
}
You should remove #Component and #Repository from CommunicationRepository if it is a spring data JPA repository.
You should define configurations in modules A and B.
#Configuration
#EnableJpaRepositories(basePackages ={"com.demo.xyz.A"})
#EntityScan(basePackages = {"com.demo.xyz.A"})
#ComponentScan(basePackages = {"com.demo.xyz.A"})
public class ConfigA {
}
// If you have no spring managed beans in B this is not needed
// If Core should be a spring managed bean, add #Component on top of it
#Configuration
#ComponentScan(basePackages = {"com.demo.xyz.B"})
public class ConfigB {
}
Then, in C, where you bootstrap the application, you should import the configurations for module A and module B. At this point, any beans from A and B will be available for autowiring in C.
#Configuration
#Import(value = {ConfigA.class, ConfigB.class})
public class ConfigC {
}
Basically if you want to use #Autowired annotation on top of any attribute and use it, Obviously there should be an initialized bean in the spring context to Autowire it to your usages. So here your problem is in your spring context, there is no such bean to autowire.
So the solution is you need to have those beans inside your spring context, there are multiple ways to get this done,
The classes that you need beans auto initialized inside the spring context as #Component
Ex :
#Component
public class Car{
or you can manually have a configuration file which returns such beans
Ex :
#Bean
public Car setCarBean(){
return new Car();
}
And this bean returning should be inside a #Configuration class.
please refer
Then if you are really sure that you have done with this, then correct #ComponentScan should work
EDIT
#SpringBootApplication
#ComponentScan(basePackages = { "com.demo.xyz.A", "com.demo.xyz.B"})
public class Application {
Try to add scanBasePackages in the Application class.
The default scan is for the package in which the Application class.
#SpringBootApplication(scanBasePackages = "com.demo.xyz")
public class Application {...}

How to Map a Java Entity to Multiple MongoDB Collections in Spring Data?

Currently, we're looking for a solution to save the following User entity into multiple MongoDB collections at the same time (i.e. db_users and on db_users_legacy). Both collections are in the same database.
Please don't ask me the reason why I need to save in two collections. It is a business requirement.
#Document(collection = "db_users")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
private String id;
private String name;
private String website;
private String name;
private String email;
}
And my SpringBoot application configuration goes as;
#Configuration
public class ApplicationConfig {
#Bean
public MongoTemplate mongoTemplate(MongoDbFactory factory){
MongoTemplate template = new MongoTemplate(factory);
template.setWriteConcern(WriteConcern.ACKNOWLEDGED);
retu,rn template;
}
}
Currently my repository looks as this. And saving works perfectly fine. How can I same this document in two different collections?
#Repository
public class UserRepositoryImpl implements UserRepository {
private MongoTemplate mongoTemplate;
public UserRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Override
public void save(User user) {
mongoTemplate.save(user);
}
}
Can anyone suggest the best option to deal with this, please?
I suggest using MongoTemplate's the other overloaded save method.
#Override
public void save(User user) {
mongoTemplate.save(user, "db_users");
mongoTemplate.save(user, "db_users_legacy");
}
This can be used to save same object to multiple collections.
From docs,
You can customize this by providing a different collection name using the #Document annotation. You can also override the collection name by providing your own collection name as the last parameter for the selected MongoTemplate method calls.
So it doesn't matter the collection name specifically provided in #Document, you can always override it using MongoTemplate.

springboot always read data from primary datasource

My springboot app tries to read data from two datasources(emwbis and backupemwbis). I've followed the below link in configuring my springboot app to read data from two different datasources.
http://www.baeldung.com/spring-data-jpa-multiple-databases
The current problem with my app is it is always reading data from the primary datasource(emwbis). I've written below code.
Model classes for primary and backup datasources:
package com.jl.models.primary;
#Entity
#Table(name = "crsbis",schema="emwbis")
#Data
public class CrsBIS {
#Id
private String id;
#NotNull
private String email;
package com.jl.models.backup;
import lombok.Data;
#Entity
#Table(name = "crsbis",schema="backupemwbis")
#Data
public class CrsBIS {
#Id
private String id;
#NotNull
private String email;
Datasource config classes for primary and backup datasources:
#Configuration
#PropertySource("classpath:persistence-multiple-db.properties")
#EnableJpaRepositories(basePackages = "com.jl.dao.backup", entityManagerFactoryRef = "crsBISBackUpEntityManager", transactionManagerRef = "crsBISBackupTransactionManager")
public class BackupCrsBISDatabaseConfig {
#Configuration
#PropertySource("classpath:persistence-multiple-db.properties")
#EnableJpaRepositories(basePackages = "com.jl.dao.primary", entityManagerFactoryRef = "crsBISEntityManager", transactionManagerRef = "crsBISTransactionManager")
public class CrsBISDatabaseConfig {
Repository interfaces for primary and backup datasources:
#Transactional
public interface CrsBISRepository extends JpaRepository<CrsBIS, String> {
public CrsBIS findById(String id);
}
#Transactional
public interface CrBisBackupRepository extends JpaRepository<CrsBIS, String>{
public CrsBIS findById(String id);
}
Persistent db proeprties file :
jdbc.driverClassName=com.mysql.jdbc.Driver
crsbis.jdbc.url=jdbc:mysql://localhost:3306/emwbis
backupcrsbis.jdbc.url=jdbc:mysql://localhost:3306/backupemwbis
jdbc.user=root
jdbc.pass=Password1
Controller class to test both the datasources :
#Controller
public class CrsBISController {
#Autowired
private CrsBISRepository crsBISRepository;
#Autowired
private CrBisBackupRepository crsBackupRepository;
#RequestMapping("/get-by-id")
#ResponseBody
public String getById(String id){
String email="";
try{
CrsBIS crsBIS = crsBISRepository.findById(id);
email = String.valueOf(crsBIS.getEmail());
}catch (Exception e) {
e.printStackTrace();
return "id not found!";
}
return "The email is : "+email;
}
#RequestMapping("/get-by-id-backup")
#ResponseBody
public String getByIdFromBackup(String id){
String email="";
try{
com.jl.models.backup.CrsBIS crsBIS = crsBackupRepository.findById(id);
email = String.valueOf(crsBIS.getEmail());
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return "id not found!";
}
return "The email is : "+email;
}
Although, I've separated the database schemas in the model classes and in the database config file, both the methods in the controller class hit the same database (emwbis). I want getByIdFromBackup method in controller class to read the data from secondary database (backupemwbis).
Can someone please let me know the mistake in my code? Or you can suggest/guide me to achieve my goal?
From the first configuration file you're creating a primary datasource bean definition with the name myDatasource and in the second emf you're injecting the same datasource reference.
The Bean causing the problem is this
#Bean
#Primary
public DataSource myDataSource()
Just change the second Bean datasource name and use it in the second EMF.
public class BackupCrsBISDatabaseConfig {
...
#Bean
public DataSource backupDS() {
....
#Bean
public LocalContainerEntityManagerFactoryBean crsBISBackUpEntityManager() {
....
em.setDataSource(backupDS());
}
}
Hope this fixes it.
You have to explicitly request a TransactionManager implementation in your #Transactional usage:
#Transactional("crsBISTransactionManager")
//..
#Transactional("crsBISBackupTransactionManager")
//..

Spring boot custom auto configuration for entities

I'm working on a Spring Boot project which consists of multiple smaller projects. They all share a common project which consists of helper classes and such. On this common project I'm trying to create a Service, Repository, Entity and a Controller which could be shared and selectively enabled along of all other projects (debug endpoints with persisted data, each project has a separate database).
I'm thinking the ideal solution for this is to create a configuration bean which should be defined in order to enable these features or something along those lines.
At the moment I have this setup. Common entity:
#MappedSuperclass
public class SomeEntity {
#Id
#GeneratedValue
protected Long id;
#Column(unique = true)
protected String name;
public SomeEntity() {
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
The service defining common methods:
public abstract class SomeEntityService<T extends SomeEntity> {
private final SomeRepository<T> someRepository;
public SomeEntityService(SomeRepository<T> someRepository) {
this.someRepository = someRepository;
}
public T getSomeEntity(String name) {
return someRepository.findByName(name);
}
public List<T> getSomeEntities() {
return someRepository.findAll();
}
public abstract void init();
}
Common controller:
#RestController
#RequestMapping(value = "/entities")
#ConditionalOnBean(value = SomeEntityService.class)
public class SomeController<T extends SomeEntity> {
private final SomeEntityService<T> someEntityService;
#Autowired
public SomeController(SomeEntityService<T> someEntityService) {
this.someEntityService = someEntityService;
}
#RequestMapping(method = RequestMethod.GET)
public List<T> getSomeEntities() {
return someEntityService.getSomeEntities();
}
#RequestMapping(value = "/{name}", method = RequestMethod.GET)
public T getSomeEntity(#PathVariable String name) {
return someEntityService.getSomeEntity(name);
}
}
Common repository:
#NoRepositoryBean
public interface SomeRepository<T extends SomeEntity> extends JpaRepository<T, Long>, JpaSpecificationExecutor<T> {
T findByName(String name);
}
The full project can be found here.
Now in this example, if I implement SomeService, SomeEntity and SomeRepository, the controller bean gets created (notice the #ConditionalOnBean annotation on the controller) and everything works fine. However I do not want to redefine the entity and repository as all the needed implementation is already there, however I cannot find any documentation on how to disable the creation of these beans based on some conditions. So the questions would be:
How can I disable the creation of specific #Entity annotated classes?
How can I do the same for #Repository annotated classes?
Is there a better way of doing this sort of thing?
Edit:
A more concrete question would be - how could I exclude selected entities from scanning based on some condition, is it possible to do this in spring?
For example, create a set of entities only is some specific bean is created or some property in application.properties file is defined.
What about using #ConditionalOnProperty and configuring it through properties files?
This article has the following to say:
In Spring Boot, you can use the #ConditionalOnProperty annotation to enable or disable a particular bean based on the presence of a property. This is very useful if you want to provide optional features to your microservice.

Categories