How to get the All table metadata in spring boot - JPA - hibernate?
Asked Answered
P

4

5

I need to get META information of All the tables present in my schema dynamically , Meta infos are such as table , entity ,column name etc.

I have followed the following tutorial

https://vladmihalcea.com/how-to-get-the-entity-mapping-to-database-table-binding-metadata-from-hibernate/

as said in the above link i have created the Integrator called MetadataExtractorIntegrator.java

package com.test.ttv;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;

public class MetadataExtractorIntegrator implements org.hibernate.integrator.spi.Integrator {

    public static final MetadataExtractorIntegrator INSTANCE = new MetadataExtractorIntegrator();

    private Database database;

    private Metadata metadata;

    public Database getDatabase() {
        return database;
    }

    public Metadata getMetadata() {
        return metadata;
    }

    @Override
    public void integrate(
            Metadata metadata,
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {

        this.database = metadata.getDatabase();
        this.metadata = metadata;

    }

    @Override
    public void disintegrate(
        SessionFactoryImplementor sessionFactory,
        SessionFactoryServiceRegistry serviceRegistry) {

    }
}

And trying to register it in my application by following config

application.yml

jpa:
  properties:
    hibernate.integrator_provider: com.test.ttv.MetadataExtractorIntegrator

And i am getting the following Exception while Starting the build

Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to org.hibernate.jpa.boot.spi.IntegratorProvider

More StackTrace

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.ClassCastException: java.lang.String cannot be cast to org.hibernate.jpa.boot.spi.IntegratorProvider
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1710) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:583) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1085) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:858) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) [spring-boot-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:388) [spring-boot-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:327) [spring-boot-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234) [spring-boot-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at testtest(Test.java:31) [main/:na]
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to org.hibernate.jpa.boot.spi.IntegratorProvider
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.buildBootstrapServiceRegistry(EntityManagerFactoryBuilderImpl.java:339) ~[hibernate-core-5.2.14.Final.jar:5.2.14.Final]
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:196) ~[hibernate-core-5.2.14.Final.jar:5.2.14.Final]
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:164) ~[hibernate-core-5.2.14.Final.jar:5.2.14.Final]
        at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:51) ~[spring-orm-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:388) ~[spring-orm-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377) ~[spring-orm-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1769) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1706) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        ... 16 common frames omitted

Could anyone help me to solve this issue?? Thanks

Penates answered 24/7, 2018 at 14:38 Comment(2)
Possible duplicate of get database metadata using hibernateIndention
Both are different, here i need to know all the tables meta info, unlike there they need to get the field info by passing table namePenates
F
17

In Spring Boot, spring.jpa.properties points to a Map<String, String>, so it can only contain String values.

However in Hibernate, when the EntityManagerFactoryBuilderImpl reads hibernate.integrator_provider it expects to find an instance of IntegratorProvider and not a Class name, hence the exception.

You can however add a bean that implements HibernatePropertiesCustomizer to add the IntegrationProvider instance to the Hibernate properties:

@Component
public class HibernateConfig implements HibernatePropertiesCustomizer {

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        hibernateProperties.put("hibernate.integrator_provider",
                (IntegratorProvider) () -> Collections.singletonList(MetadataExtractorIntegrator.INSTANCE));
    }
}

I have created a working example in this repository.

Floorboard answered 25/7, 2018 at 15:2 Comment(3)
Wow! Thanks lot!Penates
The class HibernatePropertiesCustomizer is implemented starting from spring boot 2.0.0. Still looking for a solution for earlier versions.Reuben
For earlier versions or a more idiomatic approach in Spring, set the spring.jpa.properties.hibernate.integrator_provider to the class name of a class implementing the IntegratorProvider (a class name works, the problem of the OP was that he used the name of the Integrator instead of an IntegratorProvider). You can also see that explained here: vladmihalcea.com/dto-projection-jpa-queryFasten
H
1

In org.springframework.orm.hibernate5.LocalSessionFactoryBean, there is a variable argument setter method for hibernateIntegrators which will accept one or more instances of org.hibernate.integrator.spi.Integrator

So in org.springframework.orm.hibernate5.LocalSessionFactoryBean configuration add the below property

<property name="hibernateIntegrators" ref="metadataExtractorIntegrator" />

and make the Integrator as managed bean

    package com.test.ttv;
    import org.hibernate.boot.Metadata;
    import org.hibernate.boot.model.relational.Database;
    import org.hibernate.engine.spi.SessionFactoryImplementor;
    import org.hibernate.service.spi.SessionFactoryServiceRegistry;
    import org.springframework.stereotype.Component;

    @Component  
    public class MetadataExtractorIntegrator implements org.hibernate.integrator.spi.Integrator {

        private Database database;

        private Metadata metadata;

        public Database getDatabase() {
            return database;
        }

        public Metadata getMetadata() {
            return metadata;
        }

        @Override
        public void integrate(
                Metadata metadata,
                SessionFactoryImplementor sessionFactory,
                SessionFactoryServiceRegistry serviceRegistry) {

            this.database = metadata.getDatabase();
            this.metadata = metadata;

        }

        @Override
        public void disintegrate(
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {

        }
    }
Hylophagous answered 13/6, 2019 at 7:26 Comment(0)
C
0

If you are using Springboot 1.5.x, The "HibernatePropertiesCustomizer" is not exist.

I found a solution usable from here. You can not use integrator here, but you can add all the event listeners one by one. below is my code code:

public class RootAwareInsertEventListener implements PersistEventListener {

    public static final RootAwareInsertEventListener INSTANCE = new RootAwareInsertEventListener();

    @Override
    public void onPersist(PersistEvent event) throws HibernateException {
        final Object entity = event.getObject();

        if (entity instanceof RootAware) {
            RootAware rootAware = (RootAware) entity;
            Object root = rootAware.getRoot();
            event.getSession().lock(root, LockMode.OPTIMISTIC_FORCE_INCREMENT);

            log.info("Incrementing {} entity version because a {} child entity has been inserted",
                    root, entity);
        }
    }

    @Override
    public void onPersist(PersistEvent event, Map createdAlready)
            throws HibernateException {
        onPersist(event);
    }
}
@Component
public class HibernateListenerConfigurer {

    @PersistenceUnit
    private EntityManagerFactory emf;

    @PostConstruct
    protected void init() {
        SessionFactoryImpl sessionFactory = emf.unwrap(SessionFactoryImpl.class);
        EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
        registry.getEventListenerGroup(EventType.PERSIST).appendListener(RootAwareInsertEventListener.INSTANCE);
        registry.getEventListenerGroup(EventType.FLUSH_ENTITY).appendListener(RootAwareUpdateAndDeleteEventListener.INSTANCE);

    }
}
Cyndi answered 12/4, 2019 at 6:19 Comment(0)
D
0

The thing is, that op using spring.jpa.properties.hibernate.integrator_provider property were providing org.hibernate.integrator.spi.Integrator implementation class instead of org.hibernate.jpa.boot.spi.IntegratorProvider implementation - which is desired here by EntityManagerFactoryBuilderImpl - at least in hibernate in version 5.4.32 that I'm using.

private void applyIntegrationProvider(Map integrationSettings, BootstrapServiceRegistryBuilder bsrBuilder) {
        Object integrationSetting = integrationSettings.get( INTEGRATOR_PROVIDER );
        if ( integrationSetting == null ) {
            return;
        }
        final IntegratorProvider integratorProvider = loadSettingInstance(
                INTEGRATOR_PROVIDER,
                integrationSetting,
                IntegratorProvider.class
        );

        if ( integratorProvider != null ) {
            for ( Integrator integrator : integratorProvider.getIntegrators() ) {
                bsrBuilder.applyIntegrator( integrator );
            }
        }
    }

All you need is an implementation of org.hibernate.jpa.boot.spi.IntegratorProvider which will return apropriate integrator instance/s:

package com.levitatingapes.hibernate;

import org.hibernate.integrator.spi.Integrator;
import org.hibernate.jpa.boot.spi.IntegratorProvider;

public class HibernateIntegratorProvider implements IntegratorProvider {
    @Override
    public List<Integrator> getIntegrators() {
        return List.of(new RootAwareEventListenerIntegrator());
    }
}

and then pass class name from above to property as following:

spring.jpa.properties.hibernate.integrator_provider=com.levitatingapes.hibernate.HibernateIntegratorProvider
Delphadelphi answered 27/3 at 6:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.