how to make database configuration configurable of

2020-05-01 15:01发布

问题:

I would like to get confirmation on the query "Is there any way to make database configurations configuration through database.properties using in persistance.xml". Is the below possible?

My following configuration works absolutely fine,

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="spring-data-jpa-krishna" transaction-type="RESOURCE_LOCAL">
        <class>net.javabeat.springdata.model.Address</class>
        <class>net.javabeat.springdata.model.Employee</class>

        <properties>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/test" />
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
            <property name="javax.persistence.jdbc.user" value="root" />
            <property name="javax.persistence.jdbc.password" value="root" />

            <property name="hibernate.show_sql" value="true" />
            <property name="eclipselink.logging.level" value="FINE" />
            <property name="eclipselink.ddl-generation" value="create-tables" />
        </properties>
    </persistence-unit>
</persistence>

but I've many environment to deploy the application codes like Dev, QA, UAT, SAT, SystemTest etc, so I wanted to make configuration like below
database.properties:

mysql.jdbc.driver.class=com.mysql.jdbc.Driver
mysql.jdbc.url=jdbc:mysql://localhost:3306/test
mysql.jdbc.username=root
mysql.jdbc.password=root
hibernate.show_sql=true
eclipselink.logging.level=FINE
eclipselink.ddl-generation=create-tables

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="spring-data-jpa-krishna" transaction-type="RESOURCE_LOCAL">
        <class>net.javabeat.springdata.model.Address</class>
        <class>net.javabeat.springdata.model.Employee</class>

        <properties>
            <property name="javax.persistence.jdbc.url" value="${mysql.jdbc.url}" />
            <property name="javax.persistence.jdbc.driver" value="${mysql.jdbc.driver.class}" />
            <property name="javax.persistence.jdbc.user" value="${mysql.jdbc.username}" />
            <property name="javax.persistence.jdbc.password" value="${mysql.jdbc.password}" />

            <property name="hibernate.show_sql" value="${hibernate.show_sql}" />
            <property name="eclipselink.logging.level" value="${eclipselink.logging.level}" />
            <property name="eclipselink.ddl-generation" value="eclipselink.ddl-generation" />
        </properties>
    </persistence-unit>
</persistence>

This would help me to solve my overhead. But I wonder how persistece.xml would load database.properties in order to get placeholders properly?

This How to externalize properties from JPAs persistence.xml? link has done it through JNDI, but I simply wanted to do it through database.properties file.

Edit-1: Yes, I'm using spring-Context.xml file

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd 
        http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd 
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- For consider the using of annotations foe defining Spring Bean -->
    <context:annotation-config />

    <!-- For defining Spring Bean -->
    <context:component-scan base-package="net.javabeat.springdata.beans" />

    <!-- For bootstrapping the Spring Repository -->
    <jpa:repositories base-package="net.javabeat.springdata.repository" />

    <!-- Necessary to get the entity manager injected into the factory bean -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

    <bean id="registrationBean" class="net.javabeat.springdata.beans.RegistrationBean" />


    <!-- =======  WAY-1    ========= -->
    <!-- ===== Define EclipseLink JPA Vendor Adapter ===== -->
    <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
        <property name="databasePlatform" value="org.eclipse.persistence.platform.database.MySQLPlatform" />
        <property name="generateDdl" value="false" />
        <property name="showSql" value="true" />
    </bean>

    <!-- Entity Manager Factory -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        =========== Read persistent.xml file from here  ===========
        <property name="persistenceUnitName" value="spring-data-jpa-krishna" />
        <property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
        <!-- spring based scanning for entity classes-->
        <property name="packagesToScan" value="net.javabeat.springdata.*" />
    </bean> 

    <!-- Transaction Manager -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <!-- Enable Transactional Manner -->
    <tx:annotation-driven transaction-manager="transactionManager" />

</beans>

Edit-1: I see below error now:

Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private net.javabeat.springdata.repository.EmployeeRepository net.javabeat.springdata.beans.RegistrationBean.employeeRepository; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'employeeRepository': Cannot create inner bean '(inner bean)#5d3c9c43' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5d3c9c43': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [SpringContext.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: PersistenceProvider [org.eclipse.persistence.jpa.PersistenceProvider@6201c010] did not return an EntityManagerFactory for name 'spring-data-jpa-krishna'
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
    ... 13 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'employeeRepository': Cannot create inner bean '(inner bean)#5d3c9c43' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5d3c9c43': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [SpringContext.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: PersistenceProvider [org.eclipse.persistence.jpa.PersistenceProvider@6201c010] did not return an EntityManagerFactory for name 'spring-data-jpa-krishna'
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:313)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:129)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1475)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1220)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
    ... 15 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5d3c9c43': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [SpringContext.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: PersistenceProvider [org.eclipse.persistence.jpa.PersistenceProvider@6201c010] did not return an EntityManagerFactory for name 'spring-data-jpa-krishna'
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:634)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:444)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1117)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1012)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:299)
    ... 28 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [SpringContext.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: PersistenceProvider [org.eclipse.persistence.jpa.PersistenceProvider@6201c010] did not return an EntityManagerFactory for name 'spring-data-jpa-krishna'
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1572)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
    ... 36 more
Caused by: java.lang.IllegalStateException: PersistenceProvider [org.eclipse.persistence.jpa.PersistenceProvider@6201c010] did not return an EntityManagerFactory for name 'spring-data-jpa-krishna'
    at org.springframework.orm.jpa.LocalEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalEntityManagerFactoryBean.java:90)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:318)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1631)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1568)
    ... 43 more

I made changes in pom.xml file:

<build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>${java.version}</source>
                        <target>${java.version}</target>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
        <!-- <filters>
            <filter>database.properties</filter>    
        </filters> -->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
    </build>

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="spring-data-jpa-krishna" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <class>net.javabeat.springdata.model.Address</class>
        <class>net.javabeat.springdata.model.Employee</class>

        <!-- http://docs.oracle.com/cd/E16439_01/doc.1013/e13981/cfgdepds005.htm -->
        <properties>        
            <property name="javax.persistence.jdbc.url" value="${jdbc.mysql.url}" />
            <property name="javax.persistence.jdbc.driver" value="${jdbc.mysql.driver.class}" />
            <property name="javax.persistence.jdbc.user" value="${jdbc.mysql.username}" />
            <property name="javax.persistence.jdbc.password" value="${jdbc.mysql.password}" />

            <property name="eclipselink.logging.level" value="FINE" />
            <property name="eclipselink.ddl-generation" value="create-tables" />
        </properties>
    </persistence-unit>
</persistence>

回答1:

When you're using Spring, you could choose to not use persistence.xml at all and configure the EntityManagerFactory through a FactoryBean named LocalContainerEntityManagerFactoryBean. This answer can give you an idea on how to configure a LocalContainerEntityManagerFactoryBean.

If you insist to use persistence.xml, you can add the ${...} placeholders in your persistence.xml and use, say, Maven's Resource Filtering to populate the actual values. With this approach, you should define the Property Values as the Maven properties and you won't need a separate database.properties. In this approach, you should add the properties as maven properties, something like following:

<properties>
    <mysql.jdbc.driver.class>com.mysql.jdbc.Driver</<mysql.jdbc.driver.class>
    <mysql.jdbc.url>jdbc:mysql://localhost:3306/test</mysql.jdbc.url>
    <mysql.jdbc.username>root</mysql.jdbc.username>
    <mysql.jdbc.password>root</mysql.jdbc.password>
    <hibernate.show_sql>true</hibernate.show_sql>

    <!-- You've got to be kidding me! -->
    <eclipselink.logging.level>FINE</eclipselink.logging.level>
    <eclipselink.ddl-generation>create-tables</eclipselink.ddl-generation>
</properties>

And by adding these to your build section in pom.xml:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
    </resources>
</build>

Every placeholder with ${...} syntax in your resources in src/main/resources will be replaced with their actual value, i.e. those are defined in properties section in pom.xml.