lazy_load_no_trans init commit (#8736)

* lazy_load_no_trans init commit

* refactoring

* methods naming

* Update persistence-modules/spring-boot-persistence-h2/src/test/java/com/baeldung/lazy_load_no_trans/LazyLoadNoTransPropertyOnIntegrationTest.java

Co-Authored-By: KevinGilmore <kpg102@gmail.com>

* Update persistence-modules/spring-boot-persistence-h2/src/test/java/com/baeldung/lazy_load_no_trans/LazyLoadNoTransPropertyOnIntegrationTest.java

Co-Authored-By: KevinGilmore <kpg102@gmail.com>

* Update persistence-modules/spring-boot-persistence-h2/src/test/java/com/baeldung/lazy_load_no_trans/LazyLoadNoTransPropertyOffIntegrationTest.java

Co-Authored-By: KevinGilmore <kpg102@gmail.com>

* naming

* code readability

Co-authored-by: admin <git-commit-id-plugin>
Co-authored-by: KevinGilmore <kpg102@gmail.com>
This commit is contained in:
petershatunov 2020-03-19 00:00:37 +03:00 committed by GitHub
parent 1528acf120
commit a94b56c74e
12 changed files with 332 additions and 1 deletions

View File

@ -29,12 +29,30 @@
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>db-util</artifactId>
<version>${db-util.version}</version>
</dependency>
</dependencies>
<properties>
<!-- The main class to start by executing java -jar -->
<start-class>com.baeldung.h2db.demo.server.SpringBootApp</start-class>
<spring-boot.version>2.0.4.RELEASE</spring-boot.version>
<hibernate.version>5.3.11.Final</hibernate.version>
<db-util.version>1.0.4</db-util.version>
</properties>
</project>

View File

@ -0,0 +1,13 @@
package com.baeldung.h2db.lazy_load_no_trans;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
public class LazyLoadNoTransSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(LazyLoadNoTransSpringBootApplication.class, args);
}
}

View File

@ -0,0 +1,67 @@
package com.baeldung.h2db.lazy_load_no_trans.config;
import net.ttddyy.dsproxy.listener.DataSourceQueryCountListener;
import net.ttddyy.dsproxy.listener.logging.CommonsQueryLoggingListener;
import net.ttddyy.dsproxy.listener.logging.DefaultQueryLogEntryCreator;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener;
import net.ttddyy.dsproxy.support.ProxyDataSource;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import javax.sql.DataSource;
import java.lang.reflect.Method;
@Component
public class DatasourceProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof DataSource && !(bean instanceof ProxyDataSource)) {
// Instead of directly returning a less specific datasource bean
// (e.g.: HikariDataSource -> DataSource), return a proxy object.
// See following links for why:
// https://stackoverflow.com/questions/44237787/how-to-use-user-defined-database-proxy-in-datajpatest
// https://gitter.im/spring-projects/spring-boot?at=5983602d2723db8d5e70a904
// http://blog.arnoldgalovics.com/2017/06/26/configuring-a-datasource-proxy-in-spring-boot/
final ProxyFactory factory = new ProxyFactory(bean);
factory.setProxyTargetClass(true);
factory.addAdvice(new ProxyDataSourceInterceptor((DataSource) bean));
return factory.getProxy();
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
private static class ProxyDataSourceInterceptor implements MethodInterceptor {
private final DataSource dataSource;
public ProxyDataSourceInterceptor(final DataSource dataSource) {
this.dataSource = ProxyDataSourceBuilder.create(dataSource)
.name("MyDS")
.multiline()
.logQueryBySlf4j(SLF4JLogLevel.INFO)
.listener(new DataSourceQueryCountListener())
.build();
}
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
final Method proxyMethod = ReflectionUtils.findMethod(this.dataSource.getClass(),
invocation.getMethod().getName());
if (proxyMethod != null) {
return proxyMethod.invoke(this.dataSource, invocation.getArguments());
}
return invocation.proceed();
}
}
}

View File

@ -0,0 +1,26 @@
package com.baeldung.h2db.lazy_load_no_trans.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.Immutable;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Immutable
public class Document {
@Id
private Long id;
private String title;
private Long userId;
}

View File

@ -0,0 +1,37 @@
package com.baeldung.h2db.lazy_load_no_trans.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Immutable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Immutable
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
private String comment;
@OneToMany(mappedBy = "userId")
@Fetch(FetchMode.SUBSELECT)
private List<Document> docs = new ArrayList<>();
}

View File

@ -0,0 +1,9 @@
package com.baeldung.h2db.lazy_load_no_trans.repository;
import com.baeldung.h2db.lazy_load_no_trans.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

View File

@ -0,0 +1,34 @@
package com.baeldung.h2db.lazy_load_no_trans.service;
import com.baeldung.h2db.lazy_load_no_trans.entity.User;
import com.baeldung.h2db.lazy_load_no_trans.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
@Service
public class ServiceLayer {
@Autowired
private UserRepository userRepository;
@Transactional(readOnly = true)
public long countAllDocsTransactional() {
return countAllDocs();
}
public long countAllDocsNonTransactional() {
return countAllDocs();
}
private long countAllDocs() {
return userRepository.findAll()
.stream()
.map(User::getDocs)
.mapToLong(Collection::size)
.sum();
}
}

View File

@ -0,0 +1,13 @@
spring.datasource.url=jdbc:h2:mem:mydb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
logging.level.org.hibernate.SQL=INFO
logging.level.org.hibernate.type=TRACE
spring.jpa.properties.hibernate.validator.apply_to_ddl=false
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=false
spring.jpa.open-in-view=false

View File

@ -0,0 +1,13 @@
spring.datasource.url=jdbc:h2:mem:mydb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
logging.level.org.hibernate.SQL=INFO
logging.level.org.hibernate.type=TRACE
spring.jpa.properties.hibernate.validator.apply_to_ddl=false
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
spring.jpa.open-in-view=false

View File

@ -10,4 +10,17 @@ CREATE TABLE billionaires (
INSERT INTO billionaires (first_name, last_name, career) VALUES
('Aliko', 'Dangote', 'Billionaire Industrialist'),
('Bill', 'Gates', 'Billionaire Tech Entrepreneur'),
('Folrunsho', 'Alakija', 'Billionaire Oil Magnate');
('Folrunsho', 'Alakija', 'Billionaire Oil Magnate');
insert into USER values (101, 'user1', 'comment1');
insert into USER values (102, 'user2', 'comment2');
insert into USER values (103, 'user3', 'comment3');
insert into USER values (104, 'user4', 'comment4');
insert into USER values (105, 'user5', 'comment5');
insert into DOCUMENT values (1, 'doc1', 101);
insert into DOCUMENT values (2, 'doc2', 101);
insert into DOCUMENT values (3, 'doc3', 101);
insert into DOCUMENT values (4, 'doc4', 101);
insert into DOCUMENT values (5, 'doc5', 102);
insert into DOCUMENT values (6, 'doc6', 102);

View File

@ -0,0 +1,41 @@
package com.baeldung.lazy_load_no_trans;
import com.baeldung.h2db.lazy_load_no_trans.LazyLoadNoTransSpringBootApplication;
import com.baeldung.h2db.lazy_load_no_trans.service.ServiceLayer;
import com.vladmihalcea.sql.SQLStatementCountValidator;
import org.hibernate.LazyInitializationException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertEquals;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LazyLoadNoTransSpringBootApplication.class)
@ActiveProfiles("lazy-load-no-trans-off")
public class LazyLoadNoTransPropertyOffIntegrationTest {
@Autowired
private ServiceLayer serviceLayer;
private static final long EXPECTED_DOCS_COLLECTION_SIZE = 6;
@Test(expected = LazyInitializationException.class)
public void whenCallNonTransactionalMethodWithPropertyOff_thenThrowException() {
serviceLayer.countAllDocsNonTransactional();
}
@Test
public void whenCallTransactionalMethodWithPropertyOff_thenTestPass() {
SQLStatementCountValidator.reset();
long docsCount = serviceLayer.countAllDocsTransactional();
assertEquals(EXPECTED_DOCS_COLLECTION_SIZE, docsCount);
SQLStatementCountValidator.assertSelectCount(2);
}
}

View File

@ -0,0 +1,47 @@
package com.baeldung.lazy_load_no_trans;
import com.baeldung.h2db.lazy_load_no_trans.LazyLoadNoTransSpringBootApplication;
import com.baeldung.h2db.lazy_load_no_trans.service.ServiceLayer;
import com.vladmihalcea.sql.SQLStatementCountValidator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertEquals;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LazyLoadNoTransSpringBootApplication.class)
@ActiveProfiles("lazy-load-no-trans-on")
public class LazyLoadNoTransPropertyOnIntegrationTest {
@Autowired
private ServiceLayer serviceLayer;
private static final long EXPECTED_DOCS_COLLECTION_SIZE = 6;
private static final long EXPECTED_USERS_COUNT = 5;
@Test
public void whenCallNonTransactionalMethodWithPropertyOn_thenGetNplusOne() {
SQLStatementCountValidator.reset();
long docsCount = serviceLayer.countAllDocsNonTransactional();
assertEquals(EXPECTED_DOCS_COLLECTION_SIZE, docsCount);
SQLStatementCountValidator.assertSelectCount(EXPECTED_USERS_COUNT + 1);
}
@Test
public void whenCallTransactionalMethodWithPropertyOn_thenTestPass() {
SQLStatementCountValidator.reset();
long docsCount = serviceLayer.countAllDocsTransactional();
assertEquals(EXPECTED_DOCS_COLLECTION_SIZE, docsCount);
SQLStatementCountValidator.assertSelectCount(2);
}
}