I really want to try batching insert operations for myself in spring, but there is a huge problem that is driving me insane.
As written on web, Hibernate will silently disable batching queries whenever you have entity with GenerationType.IDENTITY (https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#batch-session-batch).
Therefore, I have tried to change my entities generationType to Sequence, but whenever i do this, my project seems to be completely broken (it won't even start).
I start to think that there are several problems in compatibility of postgresql and batching operations in spring.
Could you please explain to me, why is that?
My datasource configuration method:
#Bean
public DataSource dataSource() {
Properties props = new Properties();
props.setProperty("dataSourceClassName", "org.postgresql.ds.PGSimpleDataSource");
props.setProperty("dataSource.user", "tesstuser");
props.setProperty("dataSource.password", "12345");
props.setProperty("dataSource.databaseName", "testdatabase");
props.put("dataSource.logWriter", new PrintWriter(System.out));
HikariConfig config = new HikariConfig(props);
HikariDataSource dataSource = new HikariDataSource(config);
return ProxyDataSourceBuilder.create(dataSource)
.name("Batch-Insert-Logger")
.asJson().countQuery().logQueryToSysOut().build();
}
Error message:
org.hibernate.exception.SQLGrammarException: could not extract ResultSet
PostgreSql exception:
org.postgresql.util.PSQLException: ERROR: relation "hibernate_sequence" does not exist
Related
In my Spring boot(2.0.7 RELEASE) application I am not able to manually set/override the timeout for the database connections in the application.properites file. I am using JPA, Hibernate, Tomcat connection pool and Postgres.
I've researched thoroughly and found very similar questions :
Overriding timeout for database connection in properties file
JPA query timeout parameters ignored but #Transaction annotation works
The reason I ask new question is because neither of the questions above have an accepted answer nor a confirmed working solution. I tried including each proposed solution in my application.properties file with no success.
Also, as mentioned in question 2: if I add parameter 'timeout = someSeconds' in the #Transactional annotation, the connection timeouts as expected but if I try extracting it in the application.properties it fails and timeouts for the default time. The problem here is that I want all connections to timeout in the given time not only the transactions.
Things I've tried in the application.properties (The desired timeout is 4 seconds):
spring.jpa.properties.javax.persistence.query.timeout=4000
spring.jdbc.template.query-timeout=4
spring.transaction.defaultTimeout=4
spring.datasource.tomcat.validation-query-timeout=4
Materials I've read:
http://www.masterspringboot.com/configuration/web-server/configuring-tomcat-connection-pool-on-spring-boot
https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
https://www.baeldung.com/spring-boot-tomcat-connection-pool
https://www.objectdb.com/java/jpa/query/setting#Query_Hints_
Am I missing some property? Does anyone know why the timeout can't be overridden via the application.properties file?
Thanks in advance.
There are at least 3 time-outs to configure:
Transaction timeouts, which you already did. I declared mine in the transactionManager bean:
txManager.setDefaultTimeout(myDefaultValue);
Query timeouts(which obviously does not need #transactional), which you already did and also explained here
Network timeouts(Read this excellent article).
For my case, i am using Oracle, and my bean configuration is as follows:
#Bean
public HikariDataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setDriverClassName(springDatasourceDriverClassName);
ds.setJdbcUrl(springDatasourceUrl);
ds.setUsername(springDatasourceUsername);
ds.setPassword(springDatasourcePassword);
ds.setDataSourceProperties(oracleProperties());
return ds;
}
Properties oracleProperties() {
Properties properties = new Properties();
properties.put("oracle.net.CONNECT_TIMEOUT", 10000);
properties.put("oracle.net.READ_TIMEOUT", 10000);
properties.put("oracle.jdbc.ReadTimeout", 10000);
return properties;
}
And if you do not want to configure a bean for the DataSource(which is what most people will do), you can configure the network timeout properties in application.properties:
spring.datasource.hikari.data-source-properties.oracle.net.CONNECT_TIMEOUT=10000
spring.datasource.hikari.data-source-properties.oracle.net.READ_TIMEOUT=10000
spring.datasource.hikari.data-source-properties.oracle.jdbc.ReadTimeout=10000
Depending on your datasource, but you can try this:
spring.datasource.hikari.max-lifetime=1000
spring.datasource.hikari.connection-timeout=1000
spring.datasource.hikari.validation-timeout=1000
spring.datasource.hikari.maximum-pool-size=10
I connect with jdbc:postgresql://localhost:5432/mydatabase?currentSchema=myschema.
I have the following configuration for SimpleJdbcInsert. I use Spring Boot.
#Bean
#Qualifier("simpleJdbcInsert")
public SimpleJdbcInsert simpleJdbcInsert(#Qualifier("dataSource") DataSource dataSource) {
return new SimpleJdbcInsert(dataSource).withTableName("tableX").usingGeneratedKeyColumns("id");
}
But i get the following execption:
org.springframework.dao.DataAccessResourceFailureException: Unable to locate table meta data for 'tableX' in the default schema
I don't know why, because i thought that i set the currentSchema=myschema already.
I don't want to do following because i set the currentSchema=myschema in the JDBC URL above.
new SimpleJdbcInsert(dataSource).withSchemaName("Schema1").withTableName("tableX").usingGeneratedKeyColumns("id");
I have one master db. After login with master db I have some another db. Is it possible to connect at runtime to second db and also have instanace of first db also(master db) application using spring-jdbc or hibernate,
thanks in advance.
Yes, sure. You can create as many data sources as you need. Just define them in the Spring Context and autowire in you classes. This question might help you with defining components with the same type but different names.
UPD1: you can create a datasource at runtime just like that:
DataSource ds = new DataSource();
ds.setUsername("username");
ds.setPassword("password");
ds.setDriverClassName("com.mysql.jdbc.Driver"); // or another driver
ds.setUrl("jdbc:mysql://{hostname}:{port}/{dbName}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false");
ds.setTestWhileIdle(true);
ds.setTestOnBorrow(true);
ds.setTestOnReturn(false);
ds.setValidationQuery("/* ping */ SELECT 1");
ds.setValidationQueryTimeout(1);
ds.setValidationInterval(30000);
ds.setTimeBetweenEvictionRunsMillis(30000);
ds.setMinIdle(1);
ds.setMaxWait(10000);
ds.setMaxIdle(10);
ds.setInitialSize(10);
ds.setMinEvictableIdleTimeMillis(30000);
I would like to get a datasource from a hibernate Configuration programmaticaly. Here is the code that I wrote :
public static DataSource getDatasource(Configuration configuration){
ServiceRegistry registry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
SessionFactoryImpl session = (SessionFactoryImpl)configuration.buildSessionFactory(registry);
DatasourceConnectionProviderImpl provider = (DatasourceConnectionProviderImpl) session.getConnectionProvider();
return provider.getDataSource();
}
But I got an exception while running the application :
Exception in thread "main" org.hibernate.HibernateException: Missing table: CONTACTS
at org.hibernate.cfg.Configuration.validateSchema(Configuration.java:1281)
at org.hibernate.tool.hbm2ddl.SchemaValidator.validate(SchemaValidator.java:155)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:508)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1769)
at com.heavenize.Migrations.getDatasource(Migrations.java:30)
at com.heavenize.Migrations.main(Migrations.java:60)
I am performing some database migration and I need the datasource to pass to my migration tool programmaticaly.
It seems that problem come with the fact that buildSessionFactory because hibernate is trying to map the entities with the tables in the database.
The property "hibernate.hbm2ddl.auto" is set to validate.
Is there a better way to get the datasource?
The error that you are getting has nothing to do with retrieving the DataSource. It is because Hibernate is validating the data model with the database and doesn't find it to be in syncrhonization. You can remove the hibernate.hbm2ddl.auto property completely, which will then default it to none and there won't be any validation.
In spring the HibernateTransactionManager uses the SessionFactory it was initialised with to "bind" a Session to the current thread context when creating a new transaction. Then when HibernateTemplate is used it find that bound Session and uses it.
However I found today that HTM also binds its transaction to the underlying DataSource as well as the SessionFactory (if possible). This allows code to use JdbcTemplate within the transaction scope and, provided the DataSource used by JdbcTemplate is the same as the SessionFactory uses, the Jdbc operations will participate in the transaction (using the same underlying Connection).
This bit me quite badly today when I had some code in my hibernate id allocator that was creating a DataSourceTransactionManager and JdbcTemplate to allocate ids out of a high-lo table. I was intending that this be a standalone transaction that would fetch the next high number and then commit the change to the id table. However because of the above behaviour it was actually participating in my "outer" hibernate transaction AND even worse committing it early. Suffice to say not good.
I tried playing around with transaction propogation settings (used REQUIRES_NEW) but this didn't help.
Does anyone know the best way to use JdbcTemplate within a hibernate transaction and NOT have them share a transaction, even tho they share the same DataSource?
EDIT:
I have a SessionFactory (S) which is created by the spring LocalSessionFactoryBean using a DataSource (D). The HibernateTransactionManager is created with that SessionFactory (S).
some business logic code would look like this..
hibernateTransactionOperations.execute( new TransactionCallbackWithoutResult()
{
#Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
// some transactional code here using a HibernateTemplate
// will include calls to id allocation when doing hibernateTemplate.save(obj)
}
} );
my id allocation does this (paraphrased), the DataSource below is the same (D) as the one used in the SessionFactory (S).
PlatformTransactionManager txManager = new DataSourceTransactionManager( dataSource );
TransactionOperations transactionOperations = new TransactionTemplate( txManager );
return transactionOperations.execute( new TransactionCallback<Long>()
{
public Long doInTransaction( TransactionStatus status )
{
return allocateBatchTxn( idKey, batchSize );
}
} );
When the transactionOperations execute above completes it will commit the underlying transaction which seems to be the same as the 'outer' hibernate transaction. I have confirmed this by checking locks/transactions in the DB.
Don't create a new DataSourceTransactionManager in your id allocation code. Instead use REQUIRES_NEW and the HibernateTransactionManager.
In allocateBatchTxn(), the safest way to get the JDBC connection is via Spring's DataSourceUtils.getConnection() method.
I tried it with REQUIRES_NEW - it works as expected (on HSQLDB), perhaps it's DB-dependent:
// txManager is a HibernateTransactionManager obtained from the application context
TransactionOperations transactionOperations = new TransactionTemplate( txManager );
transactionOperations.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
return transactionOperations.execute(new TransactionCallback<Long>() {
public Long doInTransaction( TransactionStatus status ) {
return allocateBatchTxn( idKey, batchSize );
}
});
Answering my own question.
The root cause of my problem is a couple of things in HibernateTransactionManager.
The setting 'autodetectDataSource' which defaults to true
In afterPropertiesSet() with the above true it auto-detects the DataSource from the SessionFactory
In doBegin() if the DataSource is not null it will bind new transactions to the SessionFactory AND the DataSource
This is causing my problem because also I have a new DataSourceTransactionManager it still uses the same underlying storage (TransactionSynchronizationManager) to manage transactions and because both use the DataSource you get this leaking of transactions between txn managers. I might argue that a txn manager should include its own 'key/id' in the key for the transactional resources so there are independent but it doesn't appear to do that.
The response above are sensible. Using the hibernate txn manager rather than creating a new DataSourceTransactionManager and then using REQURES_NEW would solve the problem. However in my case that would introduce a circular dependency between HTM -> SessionFactory -> IdAllocator -> HTM.
I came up a solution that works but isn't the most elegant thing ever.
When constructor the id allocator it is passed a DataSource in the constructor. I simply wrap that DataSource in a delegating wrapper that is 100% pass through. This changes the DataSource reference so the txn logic does not think there is a transaction in progress and works as I want it to.