I have a few milions of records and I migrate it from one Oracle DB to another. We have performance problem and after discussing with my colleagues I decided to process data multithread.
We have the following artifacts:
Spring boot 2.1.6.RELEASE
HikariCP 3.2.0
Hibernate 5.3.10.Final
JDK 11.0.2
OJDBC8 12.2.0.1
I have Service class annotated #Service and inside the class is method
#Async("threadPoolTaskExecutor")
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void processMigration(int from, int to) {
int progressInterval = to - from;
ProgressBar progressBar = new ProgressBar("Progress " + Thread.currentThread().getName(), progressInterval, ProgressBarStyle.UNICODE_BLOCK);
try (Stream<SomeEntity> entityStream = someEntityRepository.streamAllInInterval(from, to)) {
progressBar.start();
entityStream.forEach(entity -> progressBar.step());
progressBar.stop();
} catch (Exception e) {
progressBar.stop();
throw e;
}
}
inside the foreach there will be some logic for processing data and this service is injected into another class call it Migrator (containing injected ThreadPoolTaskExecutor) with the following method:
public void migrate() {
crimeService.processMigration(0, 500000);
crimeService.processMigration(500000, 1000000);
}
and my SpringBootApplication class (main configuration):
#SpringBootApplication
#EnableAsync
#EnableTransactionManagement
public class MigrationApplication {
public static void main(String[] args) {
SpringApplication.run(MigrationApplication.class, args);
}
#Bean("threadPoolTaskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(25);
executor.setQueueCapacity(30);
executor.afterPropertiesSet();
return executor;
}
}
application.yaml looks like this:
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 100
fetch_size: 400
dialect: org.hibernate.dialect.Oracle12cDialect
order_inserts: true
order_updates: true
datasource:
url: jdbc:oracle:thin::1521:something
username: username
password: password
hikari:
maximum-pool-size: 10
leak-detection-threshold: 30000
driver-class-name: oracle.jdbc.OracleDriver
I supposed that when I ran the mentioned code then Hikari will create 2 connections, because I have called twice the method processMigration() annotated with #Transactional. I saw in log that it created only at the begining, but when the one of the threads waiting to another then there was only one connection as active. My computer has available 4 cores, so I would expect that with the HW problem does not exist. I understand why there is only one active connection because there is only one thread running, but why the second thread waiting I cannot figure out. Please help and If someone have better approach how to migrate data than I chose, I appriciate your suggestion.
UPDATE
I found out that the JpaTransactionManager creates the transaction for both threads, but one transaction is committed immediately at the begining. When the second thread had finished the task, it has not finished the previous task.
2019-08-15 15:02:39.707 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.count]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2019-08-15 15:02:39.707 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(1773290233<open>)] for JPA transaction
2019-08-15 15:02:39.717 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#698ef9d1]
2019-08-15 15:02:40.156 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2019-08-15 15:02:40.157 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1773290233<open>)]
2019-08-15 15:02:40.163 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(1773290233<open>)] after transaction
Migrating 1799449 CRIMES
2019-08-15 15:02:40.186 DEBUG 3939 --- [lTaskExecutor-2] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [com.aliter.mvsmigration.dvs.service.CrimeServiceImpl.processMigration]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT
2019-08-15 15:02:40.186 DEBUG 3939 --- [lTaskExecutor-1] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [com.aliter.mvsmigration.dvs.service.CrimeServiceImpl.processMigration]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT
TASK duration: 830ms
2019-08-15 15:02:40.187 DEBUG 3939 --- [lTaskExecutor-2] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(346995150<open>)] for JPA transaction
2019-08-15 15:02:40.187 DEBUG 3939 --- [lTaskExecutor-1] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(1848267920<open>)] for JPA transaction
2019-08-15 15:02:40.187 DEBUG 3939 --- [lTaskExecutor-2] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#1a69552b]
2019-08-15 15:02:40.188 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2019-08-15 15:02:40.189 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(282270616<open>)] for JPA transaction
2019-08-15 15:02:40.191 DEBUG 3939 --- [lTaskExecutor-1] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#22c0dffa]
2019-08-15 15:02:40.192 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#817e0aa]
2019-08-15 15:02:40.212 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2019-08-15 15:02:40.214 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(282270616<open>)]
2019-08-15 15:02:40.229 DEBUG 3939 --- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(282270616<open>)] after transaction
PROGRAM duration: 1507ms
mvs-migration-shell:>2019-08-15 15:02:40.389 WARN 3939 --- [lTaskExecutor-2] org.jline : Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)
2019-08-15 15:02:40.389 WARN 3939 --- [lTaskExecutor-1] org.jline : Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)
Progress MVSThreadPoolTaskExecutor-2 0% │ │ 0/1000 (0:00:00 / ?)
2019-08-15 15:02:41.820 DEBUG 3939 --- [lTaskExecutor-2] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2019-08-15 15:02:41.821 DEBUG 3939 --- [lTaskExecutor-2] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(346995150<open>)]
2019-08-15 15:02:41.825 DEBUG 3939 --- [lTaskExecutor-2] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(346995150<open>)] after transaction
Progress MVSThreadPoolTaskExecutor-1 100% │██████████│ 1000/1000 (0:00:08 / 0:
2019-08-15 15:02:49.084 DEBUG 3939 --- [lTaskExecutor-1] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2019-08-15 15:02:49.085 DEBUG 3939 --- [lTaskExecutor-1] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1848267920<open>)]
2019-08-15 15:02:49.585 DEBUG 3939 --- [lTaskExecutor-1] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(1848267920<open>)] after transaction
2019-08-15 15:03:02.981 DEBUG 3939 --- [l-1 housekeeper] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Pool stats (total=10, active=0, idle=10, waiting=0)
2019-08-15 15:03:32.984 DEBUG 3939 --- [l-1 housekeeper] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Pool stats (total=10, active=0, idle=10, waiting=0)
2019-08-15 15:04:02.989 DEBUG 3939 --- [l-1 housekeeper] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Pool stats (total=10, active=0, idle=10, waiting=0)
2019-08-15 15:04:32.995 DEBUG 3939 --- [l-1 housekeeper] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Pool stats (total=10, active=0, idle=10, waiting=0)
I found out where the problem is, in the mentioned method processMigration() there is the method streamAllInInterval(from, to), which returns the data in specified interval. It contains native SQL query:
Select f.* from someView f left join someTable em on em.id = f.utvar where em.id is null AND ROWNUM BETWEEN :from AND :to
This query returns data when I set from 0 and to 500000 but not for 500000 and 1000000. So I rewrote the query with the first mentioned answer in here SQL ROWNUM how to return rows between a specific range and now everything is working.
Related
We have written a basic streams processor using Spring Cloud Stream (3.0.9.RELEASE) and the Kafka streams binder. The actual project contains multiple classes annotated with #SpringBootApplication, each with their own functions, but only one is used at a time.
The issue we're having is that, when bundling our app into a JAR and running it as a k8s deployment, if we run any more than 1 replica at a time, those additional replicas terminate after a few minutes with no meaningful error message or obvious reason why. For example, if we run 5 replicas, 1 will stay up indefinitely and process events, but the other 4 (although they too initially start to process events) will exit shortly after. Obviously if we set our deployment to run only 1 replica there is no issue, but the processor needs to be scalable.
This section of the documentation regarding setting an application ID seems relevant:
For production deployments, it is highly recommended to explicitly specify the application ID
through configuration. This is especially going to be very critical if you are auto scaling your
application in which case you need to make sure that you are deploying each instance with the
same application ID.
but neither setting this value at the binding level (for our normalize function):
spring:
cloud:
stream:
kafka:
streams:
binder:
functions:
normalize:
applicationId: normalizer-full
nor setting it at the binder level:
spring:
cloud:
stream:
kafka:
streams:
binder:
applicationId: normalizer-full
seems to be able to keep the subsequent pods running.
Any help appreciated.
-- EDIT --
Here is a capture of the trace logging When the processor shuts down after several minutes:
2020-11-20 06:32:09.721 DEBUG 1 --- [extShutdownHook] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext#34f7cfd9, started on Fri Nov 20 06:26:59 GMT 2020
2020-11-20 06:32:09.722 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.integration.config.IdGeneratorConfigurer#0'
2020-11-20 06:32:09.722 DEBUG 1 --- [extShutdownHook] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext#5e1dde44, started on Fri Nov 20 06:27:23 GMT 2020, parent: org.springframework.context.annotation.AnnotationConfigApplicationContext#34f7cfd9
2020-11-20 06:32:09.722 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'lifecycleProcessor'
2020-11-20 06:32:09.723 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory#38830ea: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,KStreamBinderConfiguration,org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory,org.springframework.boot.autoconfigure.kafka.KafkaAnnotationDrivenConfiguration,org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor,org.springframework.boot.context.internalConfigurationPropertiesBinderFactory,org.springframework.boot.context.internalConfigurationPropertiesBinder,org.springframework.boot.context.properties.BoundConfigurationProperties,org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata,org.springframework.cloud.stream.binder.kafka.streams.MultiBinderPropertiesConfiguration,provisioningProvider,kStreamBinder]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory#7cb502c
2020-11-20 06:32:09.723 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Retrieved dependent beans for bean 'org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory': [org.springframework.context.annotation.internalConfigurationAnnotationProcessor]
2020-11-20 06:32:09.724 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'lifecycleProcessor'
2020-11-20 06:32:09.724 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.kafka.config.internalKafkaListenerEndpointRegistry'
2020-11-20 06:32:09.724 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'outputBindingLifecycle'
2020-11-20 06:32:09.724 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'inputBindingLifecycle'
2020-11-20 06:32:09.724 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'streamsBuilderFactoryManager'
2020-11-20 06:32:09.724 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean '_org.springframework.integration.errorLogger'
2020-11-20 06:32:09.724 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'integrationHeaderChannelRegistry'
2020-11-20 06:32:09.724 TRACE 1 --- [extShutdownHook] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'stream-builder-normalize'
2020-11-20 06:32:09.724 DEBUG 1 --- [extShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147483547
...
I'm currently learning Hibernate and I'm stuck at fetching data from many-to-many relationship in hibernate (spring-jpa). I'm trying to get data by id and it just doesn't work.
I know it's not realistic but one book can be taken from many persons.
The problem is, it does not matter in which class I put fetch type EAGER and in the second I put LAZY fetch type, it's throwing:
LazyInitializationException: failed to lazily initialize a collection of role
But if I put EAGER fetch type in both classes is throwing a StackOverFlowError.
The many-to-many logic goes like this we have a books in library and we have persons and many persons can take many books and book can be taken from many persons.
I have tried to put EAGER fetching in both classes but it gives me StackOverFlowError.
I have to mention that I'm using the JpaRepository interface.
The Person class:
#Entity
#Table(name = "persons")
public class Person { // (in library)
#Id
#Column(name = "id")
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "name")
private String name;
#Column(name = "number_card")
private int numberCard;
#Column(name = "time_of_account_creating")
#Temporal(TemporalType.DATE)
private Date date;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
List<Book> books = new ArrayList<>();
public Person() {
this.date = new Date();
}
public Person(String name, int numberCard) {
this.name = name;
this.numberCard = numberCard;
this.date = new Date();
}
public void addBook(Book book) {
this.books.add(book);
}
// gettters and setters
The Book class:
#Entity
#Table(name = "books")
public class Book { // (in library)
#Id
#Column(name = "id")
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "release_year")
private int releaseYear;
#Column(name = "name")
private String name;
#ManyToMany(mappedBy = "books", cascade=CascadeType.ALL, fetch = FetchType.LAZY)
private List<Person> persons = new ArrayList<>();
public Book() {
}
public Book(int releaseYear, String name) {
this.releaseYear = releaseYear;
this.name = name;
}
public List<Person> getPersons() {
return persons;
}
public void addPerson(Person person) {
this.persons.add(person);
}
// getters and setters
Main method in the #SpringBootApplication:
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(HibernateDemoApplication.class, args);
PersonRepository personRepository = context.getBean(PersonRepository.class);
System.out.println(personRepository.findById(4));
}
application.property:
spring.datasource.url = jdbc:mysql://localhost:3306/${DB}
spring.datasource.username = ${username}
spring.datasource.password = ${password}
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update
spring.jpa.show-sql=true
The output console:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.5.RELEASE)
2020-03-26 15:59:27.228 INFO 18912 --- [ main] c.e.h.HibernateDemoApplication : Starting HibernateDemoApplication on abu with PID 18912 (/home/yoav/hibernateDemo/target/classes started by yoav in /home/yoav/hibernateDemo)
2020-03-26 15:59:27.232 INFO 18912 --- [ main] c.e.h.HibernateDemoApplication : No active profile set, falling back to default profiles: default
2020-03-26 15:59:28.450 INFO 18912 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-03-26 15:59:28.625 INFO 18912 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 131ms. Found 6 JPA repository interfaces.
2020-03-26 15:59:31.886 INFO 18912 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-03-26 15:59:31.928 INFO 18912 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-03-26 15:59:31.929 INFO 18912 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.31]
2020-03-26 15:59:32.101 INFO 18912 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-03-26 15:59:32.101 INFO 18912 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 4687 ms
2020-03-26 15:59:32.636 INFO 18912 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-03-26 15:59:33.001 INFO 18912 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.4.12.Final
2020-03-26 15:59:33.631 INFO 18912 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-03-26 15:59:35.055 INFO 18912 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-03-26 15:59:37.374 INFO 18912 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2020-03-26 15:59:37.689 INFO 18912 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL5InnoDBDialect
Hibernate: alter table students add constraint FKrpifpqwvgu2pg2lib5c787vs foreign key (laptop_id) references laptops (id)
2020-03-26 15:59:41.092 INFO 18912 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-03-26 15:59:41.105 INFO 18912 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-03-26 15:59:42.982 WARN 18912 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-03-26 15:59:43.406 INFO 18912 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-03-26 15:59:44.775 INFO 18912 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-03-26 15:59:45.009 INFO 18912 --- [ main] c.e.h.HibernateDemoApplication : Started HibernateDemoApplication in 18.339 seconds (JVM running for 18.756)
Hibernate: select person0_.id as id1_5_0_, person0_.time_of_account_creating as time_of_2_5_0_, person0_.name as name3_5_0_, person0_.number_card as number_c4_5_0_, books1_.persons_id as persons_1_6_1_, book2_.id as books_id2_6_1_, book2_.id as id1_0_2_, book2_.name as name2_0_2_, book2_.release_year as release_3_0_2_ from persons person0_ left outer join persons_books books1_ on person0_.id=books1_.persons_id left outer join books book2_ on books1_.books_id=book2_.id where person0_.id=?
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.hibernateDemo.models.Book.persons, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:621)
at java.lang.String.valueOf(String.java:2994)
And I have to say it again, I am getting a StackOverFlowError if I put EAGER fetch type in both classes.
I know this thread is a few months old, but for asker in the future:
Issue:
You hide the Person#toString method. In the method you access the books field. It is fine since the fetchtype is EAGER. The issue is that you access the Book#toString where you access the persons field. This field is LAZY. Since you will not access this field within transaction you will always get the LazyInitializationException.
Solution:
Open a transaction. (google jpa transaction)
Load the entity.
Access the lazy fields. It will work!
Close the transaction: Note, all changes applied to loaded entities within transaction will be save to database automaticallay, there is no need to save them through repository DAO manually!
I'm using an H2 embedded database for testing, and after the tests complete, I'm seeing the system trying to close the database twice and then it hangs waiting on the last log line shown here:
...
2019-07-14 07:58:47.115 INFO 44844 --- [ Thread-2] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2019-07-14 07:58:47.115 INFO 44844 --- [ Thread-4] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2019-07-14 07:58:47.116 INFO 44844 --- [ Thread-2] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2019-07-14 07:58:47.116 INFO 44844 --- [ Thread-4] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2019-07-14 07:58:47.117 INFO 44844 --- [ Thread-4] o.s.j.d.e.EmbeddedDatabaseFactory : Shutting down embedded database: url='jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false'
2019-07-14 07:58:47.117 INFO 44844 --- [ Thread-2] o.s.j.d.e.EmbeddedDatabaseFactory : Shutting down embedded database: url='jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false'
This is happening with Spring Boot 2.1.5 and 2.1.6
In the test class I set up the database this way
#RunWith(SpringRunner.class)
#SpringBootTest
#TestPropertySource(locations = "classpath:application.yml")
#Slf4j
public class DBTest {
...
static EmbeddedDatabase informixDB;
static JdbcTemplate informixJDBCTemplate;
#BeforeClass
public static void initDb() {
informixDB = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
informixJDBCTemplate = new JdbcTemplate(informixDB);
ClassPathResource initSchema = new ClassPathResource("data/informix/InformixUp.sql");
DatabasePopulator databasePopulator = new ResourceDatabasePopulator(initSchema);
DatabasePopulatorUtils.execute(databasePopulator, informixDB);
}
#AfterClass
public static void dropDb() {
ClassPathResource drop = new ClassPathResource("data/informix/InformixDown.sql");
DatabasePopulator databasePopulator = new ResourceDatabasePopulator(drop);
DatabasePopulatorUtils.execute(databasePopulator, informixDB);
}
I have this in my test/application.yml though it seems to be being ignored
spring:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
h2:
console:
path: /h2-console
enabled: true
settings:
web-allow-others: true
# trace: true
datasource:
url: jdbc:h2:mem:informixDB;AUTO_SERVER=TRUE
username: sa
password:
I wanted to make use of #Transactional, expecting that annotated methods get a separate transaction which will be committed at the end of the method.
However, if I check the DB, there was nothing committed:
#Transactional
public boolean borrowLibraryItem(Long libraryUserId, Long uniqueLibraryItemNumber) {
boolean success = false;
LibraryUser borrower = libraryUserRepository.findByLibraryUserId(libraryUserId);
LibraryItem borrowItem = libraryItemRepository.findByUniqueLibraryItemNumber(uniqueLibraryItemNumber);
success = borrower != null && borrowItem != null;
if (success) {
BorrowedByRel borrowedByRel = new BorrowedByRel(borrower, borrowItem);
borrowedByRel.setBorrowDate(LocalDateTime.now());
borrowItem.setBorrowedByRel(borrowedByRel);
// libraryItemRepository.save(borrowItem);
}
return success;
}
The code commits the changes perfectly fine when using the repository.save-method, but not without.
Configuration is done via spring boot - as far as I understood, things should work out of the box this way (this might be the part where I got something wrong):
#SpringBootApplication
#EnableNeo4jRepositories(basePackages = "yalms.libraryapi.repositories")
#EntityScan("yalms.libraryapi.entities")
#EnableTransactionManagement
public class YalmsLibraryApplication {
public static void main(String[] args) {
SpringApplication.run(YalmsLibraryApplication.class, args);
}
}
Something regarding transactions seems to be happening, as the following logging.level.org.springframework.transaction.interceptor=TRACE shows:
Getting transaction for borrowLibraryItem()..
Don't need to create transaction for findByLibraryUserId, not transactional..
Request: MATCH (n:`LibraryUser`)..
Don't need to create transaction for findByUniqueLibraryItemNumber, not transactional..
Request: MATCH (n:`LibraryItem`)..
Completing transaction for borrowLibraryItem().
I would expect though that the changes (the addition of a relationship) would be committed. Am I misunderstanding something here or do I have something not configured right? Any help would be very much appreciated, thanks in advance!
UPDATE:
I've added a Neo4jTransactionManager-Bean as suggested in the comments, unfortunately it didn't help to solve my issue:
#Bean
public SessionFactory sessionFactory() {
org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration.Builder()
.uri(databaseUrl)
.credentials(userName, password)
.build();
return new SessionFactory(configuration,"yalms.libraryapi");
}
#Bean
public Neo4jTransactionManager transactionManager() {
return new Neo4jTransactionManager(sessionFactory());
}
I've enabled trace-output regarding everything coming from spring.data.*, and it's quite surprising to me as it seems that everything seems to be working fine:
TRACE 19634 --- [nio-8080-exec-2] .s.t.s.TransactionSynchronizationManager : Initializing transaction synchronization
TRACE 19634 --- [nio-8080-exec-2] o.s.t.i.TransactionInterceptor : Getting transaction for [yalms.libraryapi.services.BorrowService.borrowLibraryItem]
TRACE 19634 --- [nio-8080-exec-2] o.s.t.i.TransactionInterceptor : Don't need to create transaction for [org.springframework.data.neo4j.repository.support.SimpleNeo4jRepository.findByLibraryUserId]: This method isn't transactional.
DEBUG 19634 --- [nio-8080-exec-2] .s.d.n.r.q.d.DerivedGraphRepositoryQuery : Executing query for method findByLibraryUserId
TRACE 19634 --- [nio-8080-exec-2] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.data.neo4j.transaction.SessionHolder#4adaed6] for key [org.neo4j.ogm.session.SessionFactory#5f4fecd0] bound to thread [http-nio-8080-exec-2]
INFO 19634 --- [nio-8080-exec-2] o.n.o.drivers.bolt.request.BoltRequest : Request: MATCH (n:`LibraryUser`) WHERE n.`libraryUserId` = { `libraryUserId_0` } WITH n RETURN n,[ [ (n)<-[r_b1:`BORROWED_BY`]-(l1:`LibraryItem`) | [ r_b1, l1 ] ] ], ID(n) with params {libraryUserId_0=0}
TRACE 19634 --- [nio-8080-exec-2] o.s.t.i.TransactionInterceptor : Don't need to create transaction for [org.springframework.data.neo4j.repository.support.SimpleNeo4jRepository.findByUniqueLibraryItemNumber]: This method isn't transactional.
DEBUG 19634 --- [nio-8080-exec-2] .s.d.n.r.q.d.DerivedGraphRepositoryQuery : Executing query for method findByUniqueLibraryItemNumber
TRACE 19634 --- [nio-8080-exec-2] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.data.neo4j.transaction.SessionHolder#4adaed6] for key [org.neo4j.ogm.session.SessionFactory#5f4fecd0] bound to thread [http-nio-8080-exec-2]
INFO 19634 --- [nio-8080-exec-2] o.n.o.drivers.bolt.request.BoltRequest : Request: MATCH (n:`LibraryItem`) WHERE n.`uniqueLibraryItemNumber` = { `uniqueLibraryItemNumber_0` } WITH n RETURN n,[ [ (n)-[r_b1:`BORROWED_BY`]->(l1:`LibraryUser`) | [ r_b1, l1 ] ] ], ID(n) with params {uniqueLibraryItemNumber_0=2}
TRACE 19634 --- [nio-8080-exec-2] o.s.t.i.TransactionInterceptor : Completing transaction for [yalms.libraryapi.services.BorrowService.borrowLibraryItem]
TRACE 19634 --- [nio-8080-exec-2] o.s.d.n.t.Neo4jTransactionManager : Triggering beforeCommit synchronization
TRACE 19634 --- [nio-8080-exec-2] o.s.d.n.t.Neo4jTransactionManager : Triggering beforeCompletion synchronization
DEBUG 19634 --- [nio-8080-exec-2] o.s.d.n.t.Neo4jTransactionManager : Initiating transaction commit
DEBUG 19634 --- [nio-8080-exec-2] o.s.d.n.t.Neo4jTransactionManager : Committing Neo4j OGM transaction [org.neo4j.ogm.drivers.bolt.transaction.BoltTransaction#5a5172dc] on Session [org.neo4j.ogm.session.Neo4jSession#7dc575ae]
TRACE 19634 --- [nio-8080-exec-2] o.s.d.n.t.Neo4jTransactionManager : Triggering afterCommit synchronization
TRACE 19634 --- [nio-8080-exec-2] .s.t.s.TransactionSynchronizationManager : Clearing transaction synchronization
TRACE 19634 --- [nio-8080-exec-2] o.s.d.n.t.Neo4jTransactionManager : Triggering afterCompletion synchronization
DEBUG 19634 --- [nio-8080-exec-2] o.s.d.n.t.Neo4jTransactionManager : Not closing pre-bound Neo4j Session after transaction
TRACE 19634 --- [nio-8080-exec-2] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.data.neo4j.transaction.SessionHolder#4adaed6] for key [org.neo4j.ogm.session.SessionFactory#5f4fecd0] from thread [http-nio-8080-exec-2]
DEBUG 19634 --- [nio-8080-exec-2] o.s.d.n.w.s.OpenSessionInViewInterceptor : Closed Neo4j OGM Session in OpenSessionInViewInterceptor
But the update still does not end up in DB - makes kinda sense as there is no query that creates the new relation, but I do not understand why not: The retrieved entities seem to be attached to the transaction, the entities get modified within the transaction, so the changes should be committed once the transaction completes. Or did I misunderstand something here fundamentally?
Neo4j-OGM (the object graph mapper behind Spring Data Neo4j) needs an explicit save call. This is currently done by the explicit Spring Data Neo4j save call. There is no auto-commit when using Spring's transactional boundary.
Basically you have no error in your application and the explicit save call needs to be un-commented.
Some notes on the comments: You do not need to define a TransactionManager on your own within a Spring Boot applications. The spring-boot-starter-neo4j takes care of initialising Configuration, SessionFactory and an appropriate TransactionManager.
I have the following repository:
public interface PlayerRealBalanceRepository extends JpaRepository<PlayerRealBalance, Long> {
#Modifying
#Query("update PlayerRealBalance balance set balance.balance = (balance.balance + ?1) where balance.id = ?2")
public void increaseBalance(long amount, long balanceId);
}
My Question - is this query thread safe? what if 2 concurrent queries like this are executed exactly at the same time? Do i have to use a lock method in order for it to work properly?
Thanks!
It will run into ACID(Atomicity, Consistency, Isolation, Durability) problem. Hence better to use transaction please refer spring transaction management here For your reference here is the log(self explanatory) what happens if you implement transaction management(though I use Mybatis)
2014-02-11 16:48:21,008 [main] DEBUG o.s.j.d.DataSourceTransactionManager - Switching JDBC Connection [jdbc:oracle:thin] to manual commit
2014-02-11 16:48:21,013 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
2014-02-11 16:48:21,021 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#16a23cf]
2014-02-11 16:48:21,091 [main] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [jdbc:oracle:thin:] will be managed by Spring
// does the db operation
2014-02-11 16:48:21,792 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#16a23cf]
2014-02-11 16:48:21,792 [main] DEBUG o.s.j.d.DataSourceTransactionManager - Initiating transaction commit
2014-02-11 16:48:21,792 [main] DEBUG o.s.j.d.DataSourceTransactionManager - Committing JDBC transaction on Connection [jdbc:oracle:thin:]
2014-02-11 16:48:21,792 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#16a23cf]
2014-02-11 16:48:21,792 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#16a23cf]
2014-02-11 16:48:21,792 [main] DEBUG o.s.jdbc.datasource.DataSourceUtils - Resetting isolation level of JDBC Connection [jdbc:oracle:thin:] to 2
2014-02-11 16:48:21,792 [main] DEBUG o.s.j.d.DataSourceTransactionManager - Releasing JDBC Connection [jdbc:oracle:thin:#] after transaction
2014-02-11 16:48:21,792 [main] DEBUG o.s.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource