I'm migrating legacy app to Spring-boot and have to integrate an hibernate named query mapping file (previously configured in persitence.xml file).
I've come out with a solution with an
...
#Autowired
private DataSource dataSource;
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
//...
sessionFactoryBean.setMappingResources("META-INF/named-queries.hbm.xml");
return sessionFactoryBean;
}
But i'm ending having an entityManager bean and a sessionFactory bean in my application!
is it a good solution according to you?
Is there a way to add somehow the hibernate mapping file (named-query.hbm.xml) to the entityManager without using the sessionFactory bean?
Thanks in advance for you suggestions
** EDIT **
fro JB Nizet's suggestion, also come up with another solution
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
// ...
entityManagerFactory.setMappingResources("META-INF/named-queries.hbm.xml");
return entityManagerFactory;
}
and in my DAO/Service, i can still get hibernate session with:
private Session getSession() {
//return this.sessionFactory.getCurrentSession();
return this.entityManager.unwrap(Session.class);
}
But if someone nows if we can do the same thing with spring-boot auto-config with properties, it's welcomed!
Put the *.hbm.xml files under the src/main/resources folder and Spring Boot can automatically scan for them.
If you want to specify the location in the application.properties file, define them to the spring.jpa.mapping-resources attribute.
spring.jpa.mapping-resources=hibernate/MyMapping.hbm.xml,hibernate/MyMapping2.hbm.xml
Tested in SpringBoot 2.1.3, following is the folder structure
src/main/resources/hibernate : Store all the *.hbm.xml files
src/main/resources/application.properties : define the spring boot properties
And if you want to get the hibernate session in your Dao classes, define them as follows:
#Repository
#Transactional
public class XxxDao {
#Autowired
private EntityManager entityManager;
private Session getSession() {
return entityManager.unwrap(Session.class);
}
...
}
#Autowired
private ResourceLoader rl;
#Bean
public LocalSessionFactoryBean sessionFactory() throws IOException {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setMappingLocations(loadResources());
}
public Resource[] loadResources() {
Resource[] resources = null;
try {
resources = ResourcePatternUtils.getResourcePatternResolver(rl)
.getResources("classpath:/hibernate/*.hbm.xml");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return resources;
}
Related
I try to implement a RESTful WebService that is able to stream millions of records directly from database.
I'm using SpringBoot 2.2.5, Hibernate 5 and PostgreSQL 11
According to this post:
https://www.airpair.com/java/posts/spring-streams-memory-efficiency
one step is needed to set the flag "allowResultAccessAfterCompletion" to true.
But how can I do this in Spring Boot?
So far I do not have any SessionFactory, EntityManagerFactory, Datasource, ... configuration in my application. Everything is autoconfigured by SpringBoot.
If I add the proposed configuration below, the application won't start because of missing SessionFactory.
#Configuration
#EnableTransactionManagement
public class DataConfig {
#Autowired #Bean
public PlatformTransactionManager txManager(SessionFactory sf) {
HibernateTransactionManager mgr = new HibernateTransactionManager(sf);
mgr.setAllowResultAccessAfterCompletion(true);
return mgr;
}
... (the rest of your data config, including the LocalSessionFactoryBean) ...
}
If I provide a SessionFactory bean by unwrapping it from EntityManagerFactory, I get another exception:
Unsatisfied dependency expressed through field 'entityManagerFactory'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'getSessionFactory': Requested bean is currently in creation: Is there an unresolvable circular reference?
Does anyone have a working configuration for my setup?
Can't this flag simply be set by some configuration value in application.properties?
Thank you!
First of all you need to decide whether you need to use Hibernate or Spring JPA for your project. Work with the framework and not against it. Using jpa classes are preferred over hibernate classes by most people today.
Since you are using springboot , the best approach is to work with the framework and use spring-boot-starter-data-jpa which will automatically configure all your necessary beans at startup. In that case, you could provide your own custom beans to override parameters as you want.
In the sample code that you provided in the question, you are using Hibernate classes directly , so you will have to manually create all the necessary beans as spring won't work with you for that unless you disable the auto-configurations which might be causing the circular dependency issue for you.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("com.sample.spring.repository")
#PropertySource("classpath:database.properties")
public class DataConfig {
private final String PROPERTY_DRIVER = "driver";
private final String PROPERTY_URL = "url";
private final String PROPERTY_USERNAME = "user";
private final String PROPERTY_PASSWORD = "password";
private final String PROPERTY_SHOW_SQL = "hibernate.show_sql";
private final String PROPERTY_DIALECT = "hibernate.dialect";
#Autowired
Environment environment;
#Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean lfb = new LocalContainerEntityManagerFactoryBean();
lfb.setDataSource(dataSource());
lfb.setPersistenceProviderClass(HibernatePersistence.class);
lfb.setPackagesToScan("com.sample.spring");
lfb.setJpaProperties(hibernateProps());
return lfb;
}
#Bean
DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl(environment.getProperty(PROPERTY_URL));
ds.setUsername(environment.getProperty(PROPERTY_USERNAME));
ds.setPassword(environment.getProperty(PROPERTY_PASSWORD));
ds.setDriverClassName(environment.getProperty(PROPERTY_DRIVER));
return ds;
}
Properties hibernateProps() {
Properties properties = new Properties();
properties.setProperty(PROPERTY_DIALECT, environment.getProperty(PROPERTY_DIALECT));
properties.setProperty(PROPERTY_SHOW_SQL, environment.getProperty(PROPERTY_SHOW_SQL));
return properties;
}
#Bean
JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
You can very well provide your own the SessionFactory as well in which case spring boot will not create another one for you. Following is excerpt from Bootstrapping Hibernate 5 with Spring article, feel free to tweak it as per your needs
#Configuration
#EnableTransactionManagement
public class HibernateConf {
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(
{"com.baeldung.hibernate.bootstrap.model" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
dataSource.setUsername("sa");
dataSource.setPassword("sa");
return dataSource;
}
#Bean
public PlatformTransactionManager hibernateTransactionManager() {
HibernateTransactionManager transactionManager
= new HibernateTransactionManager();
transactionManager.setAllowResultAccessAfterCompletion(true);
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
private final Properties hibernateProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty(
"hibernate.hbm2ddl.auto", "create-drop");
hibernateProperties.setProperty(
"hibernate.dialect", "org.hibernate.dialect.H2Dialect");
return hibernateProperties;
}
}
Another approach is to use BeanPostProcessor if you know that spring boot is already creating a HibernateTransactionManager in it's own lifecycle. Following is what the outline of the this BeanPostProcessor would look like
public class HTMPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof HibernateTransactionManager) {
((HibernateTransactionManager)bean).setAllowResultAccessAfterCompletion(true);
}
return bean; // you can return any other object as well
}
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean; // you can return any other object as well
}
}
Hope this helps!!
I would like to have different projects that consume the same database configuration from other spring project, I have the next Database configuration in each project with the application.properties too:
#Configuration
#EnableTransactionManagement
public class DatabaseConfiguration {
#Bean
public LocalSessionFactoryBean sessionFactory(){
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
sessionFactoryBean.setPackagesToScan("xxxx");
sessionFactoryBean.setHibernateProperties(hibernateProperties());
return sessionFactoryBean;
}
#Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.teradata.jdbc.TeraDriver");
dataSource.setUrl("jdbc:teradata://xxxx");
return dataSource;
}
#Bean(name = "properties")
public Properties hibernateProperties(){
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.TeradataDialect");
return properties;
}
}
I suppose that the sessionFactory() method should be in each project, but the others could be in a DatabaseConfig project. I would like to use rest if it's necessary between them.
Is it possible?
Thanks.
Do you share the same database and perform same operations? If so, implement it as dao and simply include it as jar in every project, that needs it.
This question already has answers here:
What exactly is Field Injection and how to avoid it?
(4 answers)
Spring setter injection and constructor injection
(2 answers)
Spring Auto Components Scanning with Constructor injection
(3 answers)
Closed 4 years ago.
I have library module which I want to use to store Hibernate models. I ask have Spring WAR package which I want to use.
Main Spring WAR:
#Configuration
#EnableTransactionManagement
public class ContextDatasource {
#Bean
public LocalSessionFactoryBean sessionFactory() throws NamingException {
final LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "org.plugin.database.models" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() throws NamingException {
return (DataSource) new JndiTemplate().lookup("java:/global/production_gateway");
}
#Bean
public PlatformTransactionManager hibernateTransactionManager() throws NamingException {
final HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private final Properties hibernateProperties() {
final Properties hibernateProperties = new Properties();
return hibernateProperties;
}
}
Library DAO:
public class BlacklistsDaoHibernate implements BlacklistsDao {
Session session;
#Autowired
SessionFactory sessionFactory;
public BlacklistsDaoHibernate() {
session = sessionFactory.getCurrentSession();
}
#Override
public void saveOrUpdate(BlacklistsModel blacklistsModel) throws Exception {
try {
session.getTransaction().begin();
session.saveOrUpdate(blacklistsModel);
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
throw new Exception("Error");
}
}
But I get NPE at this line session = sessionFactory.getCurrentSession();
What is the proper way to use sessionFactory into the Library Jar module?
You can use #PostConstruct or constructor injection. Spring instantiates your object, then resolves its #Autowired fields. You can ask that it resolve your object's dependencies prior to instantiating the object via constructor injection.
With constructor injection:
SessionFactory sessionFactory;
Session session;
public BlacklistsDaoHibernate(#Autowired SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
this.session = sessionFactory.getCurrentSession();
}
With #PostConstruct:
#Autowired
SessionFactory sessionFactory;
Session session;
#PostConstruct
void init() {
this.session = sessionFactory.getCurrentSession();
}
Edit: If your goal is just to resolve the NullPointerException as mentioned in the comments, use this instead:
#PostConstruct
void init() {
this.session = sessionFactory.openSession();
}
There's an example of the typical usage of SessionFactory at https://www.java2novice.com/hibernate/session-factory.
Maybe it is kind of too common but still.
I have a small test project where I'm testing all the JPA stuff. Almost everywhere I'm using Spring Data and JPA repositories work just fine. But now I'm trying to make my service to save entities. The service looks something like this:
#Service
public class SomeServiceImpl implements SomeService {
#Autowired
private EntityManagerFactory entityManagerFactory;
public SomeServiceImpl(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
#Override
#Transactional
public SomeEntity save(SomeEntity someEntity) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.persist(someEntity);
return someEntity;
}
The persistence config looks like this (I'm intentionally copying and pasting the whole config. Maybe it would help you to reproduce the error):
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories
#PropertySource({"classpath:conf/application.properties"})
public class PersistenceConfig {
#Autowired
private Environment environment;
#Bean
public DataSource dataSource() throws SQLException {
PoolDataSourceImpl dataSource = new PoolDataSourceImpl();
dataSource.setConnectionFactoryClassName(environment.getRequiredProperty("db.driverClassName"));
dataSource.setURL(environment.getRequiredProperty("db.url"));
dataSource.setUser(environment.getRequiredProperty("db.username"));
dataSource.setPassword(environment.getRequiredProperty("db.password"));
dataSource.setFastConnectionFailoverEnabled(
Boolean.valueOf(environment.getRequiredProperty("db.fast.connect.failover.enabled")));
dataSource.setValidateConnectionOnBorrow(true);
dataSource.setSQLForValidateConnection("SELECT SYSDATE FROM DUAL");
dataSource.setONSConfiguration(environment.getRequiredProperty("db.ons.config"));
dataSource.setInitialPoolSize(Integer.valueOf(environment.getRequiredProperty("db.initial.pool.size")));
dataSource.setMinPoolSize(Integer.valueOf(environment.getRequiredProperty("db.min.pool.size")));
dataSource.setMaxPoolSize(Integer.valueOf(environment.getRequiredProperty("db.max.pool.size")));
dataSource.setAbandonedConnectionTimeout(0);
dataSource.setInactiveConnectionTimeout(60 * 25);
dataSource.setTimeToLiveConnectionTimeout(0);
dataSource.setMaxConnectionReuseTime(60 * 30L);
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.setProperty("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.setProperty("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
properties.setProperty("hibernate.hbm2ddl.auto", environment.getRequiredProperty("hibernate.hbm2ddl.auto"));
return properties;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(#Autowired DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setPackagesToScan("com.dropbinc.learning.jpa.model");
JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
Map jpaProperties = new HashMap();
jpaProperties.put("javax.persistence.schema-generation.database.action", "drop-and-create");
entityManagerFactory.setJpaPropertyMap(jpaProperties);
entityManagerFactory.setJpaProperties(hibernateProperties());
return entityManagerFactory;
}
#Bean
public PlatformTransactionManager transactionManager(#Autowired EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
And one more where I'm planning to configurate the rest of the application (placed in the same package):
#Configuration
#ComponentScan(basePackages = "com.dropbinc.learning.jpa")
public class AppConfig {
}
I've tried to debug Spring but all that I wasn't able to detect a difference between transaction behaviour of JPA repositories and my service. I saw transaction was created and even commited. But in case of JPA repositories it got saved while in my service implementation it did generated ids but an entity didn't appeared in a database.
I'm running all the stuff in tests, autowiring the service by interface:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { AppConfig.class, PersistenceConfig.class })
public class AllTheTests {
#Autowired
SomeService someService;
...
}
Thank you very much for any suggestion!
EDIT Adding entityManager.flush() call generates nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress.
I'm getting exception Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Building is not mapped [from Building]
My Building class mapped
#javax.persistence.Entity
#Table(name = "building")
public class Building extends AbstractModel {
AbstractModel is empty (just for upcast)
Setting packagesToScan
#Primary
#Bean
#Autowired
public EntityManagerFactory entityManagerFactory(DataSource dataSource) {
....
localContainerEntityManagerFactoryBean.setPackagesToScan("com.app.persistence.model");
....
}
Code throws excetion
public <M extends AbstractModel> List<M> findAll() {
List<Building> buildings;
try {
buildings = (List<Building>) getHibernateTemplate().find("from Building");
} catch (Exception e) {
throw e;
}
return (List<M>) buildings;
}
Also i setuped
#Bean
public LocalSessionFactoryBean localSessionFactoryBean(DataSource ds) throws ClassNotFoundException {
LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
localSessionFactoryBean.setDataSource(dataSource());
return localSessionFactoryBean;
}
You are configuring an EntityManagerFactory which is for use with JPA however in your code you are using the plain Hibernate API, which requires a correctly configured SessionFactory instead.
Instead of using plain hibernate I strongly suggest to simply use JPA instead. Just rewrite your code to use an EntityManager instead of Session and/or HibernateTemplate (The latter is something you should avoid using as that isn't recommended anymore since hibernate 3.0.1!).
#PersistenceContext
private EntityManager em;
public <M extends AbstractModel> List<M> findAll() {
return em.createQuery("select b FROM Building b", Building.class).getResultList();
}
And remove the setup of plain hibernate i.e. the LocalSessionFactoryBean configuration and HibernateTemplate setup.
This is all you need. Now if you would add Spring Data JPA into the mix you don't even need this, you would only need a BuildingRepository interface.
public interface BuildingRepository extends JpaRepository<Building, Long> {}
Assuming that the id is of type Long.
If you really want to use plain Hibernate (which as stated is something I wouldn't recommend) you need to configure your LocalSessionFactoryBean correctly and specify the packagesToScan for it as well.
#Bean
public LocalSessionFactoryBean localSessionFactoryBean(DataSource ds) throws ClassNotFoundException {
LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
localSessionFactoryBean.setDataSource(dataSource());
localSessionFactoryBean.setPackagesToScan("com.app.persistence.model");
return localSessionFactoryBean;
}