Can't set JPA naming strategy after configuring multiple data sources (Spring 1.4.1 / Hibernate 5.x)
Asked Answered
D

6

77

I am using Spring Boot 1.4.1 which uses Hibernate 5.0.11. Initially I configured a data source using application.properties like this:

spring.datasource.uncle.url=jdbc:jtds:sqlserver://hostname:port/db
spring.datasource.uncle.username=user
spring.datasource.uncle.password=password
spring.datasource.uncle.dialect=org.hibernate.dialect.SQLServer2012Dialect
spring.datasource.uncle.driverClassName=net.sourceforge.jtds.jdbc.Driver

I configured it with "uncle" because that will be the name of one of multiple data sources that I'll configure. I configured this data source like this, according to the Spring docs:

@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.uncle")
public DataSource uncleDataSource() {
    return DataSourceBuilder.create().build();
}

At this point everything worked fine.

I created an @Entity class without any @Column annotations and let Hibernate figure out the column names, for example if I have a Java property named idBank, Hibernate will automatically assume the column name is id_bank. This is used when generating ddl, running SQL statements, etc. I want to utilize this feature because I'm going to have a lot of entity classes and don't want to have to create and maintain all of the @Column annotations. At this point, this worked fine.

I then added another data source like this:

spring.datasource.aunt.url=jdbc:sybase:Tds:host2:port/db2
spring.datasource.aunt.username=user2
spring.datasource.aunt.password=password2
spring.datasource.aunt.dialect=org.hibernate.dialect.SybaseDialect
spring.datasource.aunt.driverClassName=com.sybase.jdbc4.jdbc.SybDriver

... and also this, following the Spring docs for setting up multiple data sources. Apparently once you define a 2nd data source, it can't configure the default beans and you have to define your own EntityManager and TransactionManager. So in addition to the data source configured above, I added these configurations:

@Bean
@Primary
PlatformTransactionManager uncleTransactionManager(@Qualifier("uncleEntityManagerFactory") final EntityManagerFactory factory) {
    return new JpaTransactionManager(factory);
}

@Bean
@Primary
LocalContainerEntityManagerFactoryBean uncleEntityManagerFactory(
        EntityManagerFactoryBuilder builder) {
    return builder
            .dataSource(uncleDataSource())
            .packages(Uncle.class)
            .persistenceUnit("uncle")
            .build();
}

@Bean
@ConfigurationProperties(prefix = "spring.datasource.aunt")
public DataSource auntDataSource() {
    return DataSourceBuilder.create().build();
}

@Bean
PlatformTransactionManager auntTransactionManager(@Qualifier("auntEntityManagerFactory") final EntityManagerFactory factory) {
    return new JpaTransactionManager(factory);
}

@Bean
LocalContainerEntityManagerFactoryBean auntEntityManagerFactory(
        EntityManagerFactoryBuilder builder) {
    return builder
            .dataSource(auntDataSource())
            .packages(Aunt.class)
            .persistenceUnit("aunt")
            .build();
}

This works in terms of connecting to the database and trying to fetch data.

HOWEVER (and here's the problem, thanks for reading this far). After these configurations I have lost the implied naming strategy that translates Java column names to snake case names, so now if I have a Java property idBank it incorrectly uses column name idBank instead of id_bank. I would really like to get that functionality back.

There is a JPA property for this spring.jpa.hibernate.naming-strategy, and there are various naming strategy classes in Spring and Hibernate such as org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy. So I tried setting it like this:

spring.jpa.hibernate.naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy

But it did not work. I tried some variations such as:

spring.datasource.uncle.naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy

and

spring.datasource.uncle.hibernate.naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy

but this did not have any effect.

Then I read that in Hibernate 5, the naming strategy was broken up into two parts, "physical" and "implicit" and there are different settings for each. So I tried this, with a few variations:

spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

and

spring.jpa.hibernate.naming.implicit-strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy

and

spring.datasource.uncle.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

and

spring.datasource.uncle.hibernate.naming.implicit-strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy

But none of these worked.

It seems like there should be a way for me to set this configuration in the beans directly, such as on the SessionFactory, but I could not find that API. The documentation around this seems to have some gaps.

I'd really like to avoid setting up a persistence.xml also, which I have not needed up to this point.

So here is where I'm stuck and I'm hoping someone can help out. Really what I would like is a way to debug these property settings, I turned on trace logging in both org.springframework and org.hibernate but there was nothing useful there. I tried stepping through the code when these beans were configured but couldn't find the place where this happens. If anyone has that info and could share it I'd be really grateful.

Displant answered 9/11, 2016 at 14:30 Comment(0)
K
129

I had the same problem and fixed it with the following code (adapted to the code in the question - for a single entity manager):

protected Map<String, Object> jpaProperties() {
    Map<String, Object> props = new HashMap<>();
    props.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
    props.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
    return props;
}

@Primary
@Bean(name = "defaultEntityManager")
public LocalContainerEntityManagerFactoryBean defaultEntityManagerFactory(
    EntityManagerFactoryBuilder builder) {
    return builder
        .dataSource(auntDataSource())
        .packages(Aunt.class)
        .persistenceUnit("aunt")
        .properties(jpaProperties())
        .build();
}
Kailakaile answered 9/11, 2016 at 16:10 Comment(8)
Thank you this did the trick! Can I ask how you figured this out? I spent quite a lot of time going through the documentation, source code, and looking at examples. I could not figure out how to debug the properties or what names Spring and Hibernate expect them to be.Displant
I was using a debugger and saw in MetadataBuilderImpl the name of the property that hibernates reads. Sometimes this is the only remaining solution, if one is absolutely frustrated, because the needed information seems to be not available. ;-) Perhaps it can be even more simplified by using the @ConfigurationProperties annotation on the EntityManagerFactory, like done on the DataSource. But it didn't function for me.Kailakaile
Are we able to do this in the properties file instead?Neighboring
Hi, could you refer some document for detail? I want to log the sql, but the key not work "hibernate.show-sql"Genagenappe
Its working for me. Very less information available about it. Thanks @UdornColorfast
this does not work for me. i am spending lot of time. working with multi tenant connection provider.Myrtamyrtaceous
is there any working example with LocalSessionFactoryBeanCavorilievo
Deprecate SpringPhysicalNamingStrategy use CamelCaseToUnderscoresNamingStrategy instead. GitHubOva
F
36

The same as @ewert answer can be gained using properties:

# this works
spring.jpa.properties.hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
spring.jpa.properties.hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

# but that doesn't work
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
spring.jpa.hibernate.naming.implicit-strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
Fenny answered 18/9, 2019 at 5:45 Comment(4)
This worked for me and I find this a cleaner solution then setting the properties in code. ThxMarchland
After I add one more datasource, naming strategy for all of my tables has been changed. This solution back my previous naming stategy. I think the issue was in manual datasources, etc.. configurationRhonarhonchus
Two years later... This worked for me too, thank you for saving my time !Bobcat
Is this discrepancy documented anywhere? Thanks btw it worked.Cumin
T
7

I think I can explain why the default behaviour disappears, as per your latest question.

As of Spring Boot 2.4.2 the deafult configuration kicks in in this method of JpaBaseConfiguration:

    @Bean
    @Primary
    @ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder) {
        Map<String, Object> vendorProperties = getVendorProperties();
        customizeVendorProperties(vendorProperties);
        return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan()).properties(vendorProperties)
                .mappingResources(getMappingResources()).jta(isJta()).build();
    }

it happens within the customizeVendorProperties method call.

By creating your own LocalContainerEntityManagerFactoryBean bean (two of them actually) this customization is not performed anymore.

Telepathy answered 21/1, 2021 at 12:12 Comment(1)
are you sure about this? I had a look at the source and don't see any related code to configure NamingStrategy being called in customizeVendorPropertiesRelive
B
2

If you are using SessionFactory you should use next lines to set naming strategies.

sessionFactory.setImplicitNamingStrategy(SpringImplicitNamingStrategy.INSTANCE);
sessionFactory.setPhysicalNamingStrategy(new SpringPhysicalNamingStrategy());
Ballflower answered 22/8, 2019 at 21:24 Comment(0)
O
2

Topup on @ewert answer:

SpingBoot 3.1.5

Deprecate SpringPhysicalNamingStrategy use CamelCaseToUnderscoresNamingStrategy instead
github.

props.put("hibernate.physical_naming_strategy", CamelCaseToUnderscoresNamingStrategy.class.getName());
props.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
Ova answered 17/11, 2023 at 8:53 Comment(1)
Thanks took me some while to find the correct class name :p. Upgraded my springboot to 3 and messed up the old configuration.Kartis
P
-1

The only way I get this running properly with Spring-Boot 2+ was setting the following manually:

  @Bean(name = "myEmf")
  public LocalContainerEntityManagerFactoryBean sapEntityManagerFactory(
      EntityManagerFactoryBuilder builder, @Qualifier("myDataSource") DataSource dataSource) {
    return builder
        .dataSource(dataSource)
        .packages("my.custom.package")
        .persistenceUnit("myPu")
        .properties(getProperties())
        .build();
  }

  public Map<String, String> getProperties() {
   val props = new HashMap<String, String>();

    if (isTest()) {
      props.put("hibernate.hbm2ddl.auto", "create");
    } else {
      props.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL95Dialect");
    }
    return props;
  }
Paperhanger answered 2/10, 2019 at 22:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.