transaction tutorial - test rollback - java

I am trying to learn how to use transaction in java spring. I am a java novice so please bear with me :-) The unit test im trying to achieve below is testing:
rollback if a runtime exception is thrown.
The problem im having is a
java.lang.NullPointerException?
Ok here goes.. I have stripped out some code to help improve readability
TutorialTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = AppConfig.class, loader=AnnotationConfigContextLoader.class)
public class TutorialTest {
#Autowired
private Dao dao;
#Test
public void that_if_a_runtime_exception_is_thrown_transaction_rolledback(){
User u = new User();
u.setUsername("FAIL_TEST");
u.setPhone("0161");
u.setEmail("FAIL_TEST#gmail.com");
//dao.addUser(u); // -- this will insert if uncommented out so I know it works
OuterService os = new OuterService();
os.addUserThrowError(u);
}
}
OuterService.java
#ContextConfiguration(classes = AppConfig.class, loader=AnnotationConfigContextLoader.class)
public class OuterService {
#Autowired
private Dao dao;
#Transactional
public void addUserThrowError(User user) throws RuntimeException{
dao.addUser(user); // gives me a java.lang.NullPointerException?
throw new RuntimeException("This should roll back DB entry");
}
}
Beans are declared in
AppConfig.java
#Configuration
#EnableTransactionManagement
#ComponentScan(value = {"com.training.spring.tx.tutorial.dao",
"com.training.spring.tx.tutorial.service"})
public class AppConfig {
public DataSource dataSource() {
// Create a BasicDataSource object and configure database
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/spring_training_tx");
dataSource.setUsername("training");
dataSource.setPassword("training");
return dataSource;
}
#Bean
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
#Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(transactionManager().getDataSource());
}
}

First of all, to quote the javadoc of #ContextConfiguration
#ContextConfiguration defines class-level metadata that is used to
determine how to load and configure an ApplicationContext for
integration tests.
Consider how you are using it on OuterService. Does it seem right? Is OuterService meant to be used to load and configure an ApplicationCOntext for integration tests? Unless I'm missing something essential, the answer is: No.
So what is OuterService? It's some kind of service. You seem to want to use it as a bean. What is a bean? A bean is an object whose lifecycle is managed by Spring. This includes instantiation of the bean class, initialization of the object, post processing, and, finally, destruction of the object.
If you created the object like so
OuterService os = new OuterService();
then Spring is not involved. You created the object and there is no way for Spring to hook into that. You cannot therefore expect Spring to autowire its field
#Autowired
private Dao dao;
And since you haven't initialized the field, it remains null, which causes the NullPointerException.
So how do we get an OuterService bean, which is managed by Spring? You either declare a #Bean method for OuterService or you annotate OuterService with #Component or any of its specializations and component-scan the package it is in. You then inject the bean into any other bean that uses it. For example,
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = AppConfig.class, loader=AnnotationConfigContextLoader.class)
public class TutorialTest {
#Autowired
private Dao dao;
#Autowired
private OuterService os;
You can then use that variable directly.

Related

#ConditionalOnBean not working with JdbcTemplate.class

I have some spring component:
#Component
#ConditionalOnBean(value={JdbcTemplate.class})
public class DictionaryHandler implements Handler {
private final JdbcTemplate jdbcTemplate;
#Autowired
public DictionaryHandler(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//some methods
}
In debug, I see, that JdbcTemplate bean was created:
But, my bean not created.It doesn't go into the constructor. But why? Bean JdbcTemplate is exist, but my bean not created.
Does the condition work wrong? I don't want create DictionaryHandler if JdbcTemplate is missing. Without this I get an error.
You should use #ConditionalOnBean on auto configuration classes only (otherwise the order is unspecified)
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.
Since your JdbcTemplate bean is defined inside JdbcTemplateAutoConfiguration class so I assume that JdbcTemplateAutoConfiguration is marked with #Configuration.
In that case, you can ensure the instantiate of your bean by config:
#Configuration
#AutoConfigureAfter(JdbcTemplateAutoConfiguration.class)
public class DicationaryHandlerConfiguration {
#Bean
#ConditionalOnBean(JdbcTemplate.class)
public DictionaryHandler handler(JdbcTemplate jdbcTemplate) {
return new DictionaryHandler(jdbcTemplate)
}
}
public class DictionaryHandler implements Handler {
private final JdbcTemplate jdbcTemplate;
public DictionaryHandler(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//some methods
}
Check the documentation for ConditionalOnBean. It runs once and if the bean it requires not yet created - it will not trigger. The order of bean creation matters. You can try to lower priority of your component with #Order annotation or increase priority of you configuration/component class which holds JdbcTemplate bean.
From Annotation Type ConditionalOnBean:
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.
In your case, the problem is DictionaryHandler bean is attempted to be created before processing the configuration class and given that JdbcTemplate bean is not yet in DI container, your DictionaryHandler is not getting instantiated.
One workaround could be to initialize DictionaryHandler in the same configuration class as JdbcTemplate.
Also, you can use different configuration class, but you'll have to use #DependsOn annotation.
An example:
#Component
#ConditionalOnBean(name = "bean1")
class AnotherBean {
}
#Configuration
class Config {
#Bean
public Object bean1() {
return new Object();
}
}
In the previous example AnotherBean will not be created, but it will be in this way:
#Configuration
class Config {
#Bean
public Object bean1() {
return new Object();
}
#Bean
#ConditionalOnBean(name = "bean1")
public AnotherBean anotherBean() {
return new AnotherBean();
}
}

Spring Boot, Mockito, injecting mock into scope session bean

I'm having a problem injecting mock into one class I need for testing. I'm trying to mock a Dao class and had no problem doing so using ReflectionTestUtils in various services I'm using, however this one just does not want to work, it keeps calling the Dao class and getting errors from the database.
This is the test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class DedicationControllerTest extends AbstractRestTest {
#Mock
UserDaoImpl userDao;
#Autowired
#InjectMocks
GrantedAuthoritiesLevelsHolder grantedAuthoritiesLevelsHolder;
#Test
public void shouldTest() throws Exception {
//given
String json = this.getJsonFromFile("json/my.json");
Mockito.when(userDao.getUser(Mockito.anyString())).thenReturn(new User(1l, "mock"));
ReflectionTestUtils.setField(grantedAuthoritiesLevelsHolder, "userDao", userDao);
ResultActions result = mockMvc.perform(post( controllerUrl + "/action")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(json));
// then
result
.andExpect(status().isOk());
}
}
And this is the class I'm trying to inject mock into:
#Component
#Scope(value="session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class GrantedAuthoritiesLevelsHolder {
#Autowired
private UserDao userDao;
// some methods
}
You will have to register mocked bean as UserDao when the context is getting loaded. You can register it as shown below. Put this in any class annotated with #Configuration
#Bean
#Primary
public UserDao UserDao() {
return mock(UserDao.class);
}
I believe that your configuration may be not enough to put a mock into Spring context.
My advice:
#MockBean(answer=Answers.RETURNS_SMART_NULLS)
UserDao userDao;
#Autowired
GrantedAuthoritiesLevelsHolder grantedAuthoritiesLevelsHolder;
It should put a mock into Spring context, moreover it should give you hints with incorrect/missing stubbing.

Autowired object is getting null

I am using Spring Boot for my application. I am defining JNDI name in the application.properties file.
When I am trying to get JdbcTemplate in below class, its null:
#Configuration
public class DemoClass
{
#Autowired
private JdbcTemplate template;
#Bean
private DataSource getDS(){
return template.getDataSource(); //NPE
}
}
Another Class
#Component
public class SecondClass {
#Autowired
private JdbcTemplate template;
public void show(){
template.getDataSource(): // Working Fine
}
}
I am not sure this configured by default.. In case it is not, then maybe you can try configuring it yourself:
#Autowired
DataSoure dataSource;
#Bean
public JdbcTemplate getJdbcTemplate() {
return new JdbcTemplate(dataSource);
}
in any case if you need only the DataSource, I think it is auto-configured by Spring Boot so you can autowire it directly when you need it.
#Repository
public class DataRepository {
private JdbcTemplate jdbcTemplate;
#Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public int updateCandidate() {
return this.jdbcTemplate.update("update .... from table where ....");
}
}
application.properties
database connection details
spring.datasource.url=jdbc:oracle:thin:***
spring.datasource.username=Scott
spring.datasource.password=Tiger
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.tomcat.initial-size=1
spring.datasource.tomcat.max-active=1
spring.datasource.tomcat.min-idle=1
spring.datasource.tomcat.max-idle=1
If you're getting a NPE at getDS. This means JdbcTemplate hasn't been injected yet, maybe it couldn't be injected.
Give spring a hint at bean dependencies by
#Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource)
}
Or
#Bean
#DependsOn("template")
public DataSouce getDS(){
return template.getDataSource();
}
By default #Autowired sets required=true so the DemoClass should not be constructed by Spring.
Most likely you are creating new DemoClass() or disabled the annotation config altogether and the DemoClass class is registered manually e.g. using XML.
Instead ensure that the DemoClass class is discovered using Spring's component scan e.g. using #SpringBootApplication or #ComponentScan e.g. as per this example.

How to initialize beans in proper order if some beans are present in test mode only?

Good day. My Spring Boot app uses Postgress database. For tests it uses H2 database. When running in non-test mode beans need to be initialized in this order:
1) Init DataSource
2) Init JPA beans
When running in test mode I need to create and populate H2 database before JPA beans initialization:
1) Init DataSource
2) Init DataSourceInitializer
3) Init JPA beans
The problem is that JPA beans get initialized before DataSourceInitializer (step 3 precedes step 2) and test fails on missing tables (hibernate.hbm2ddl.auto=validate).
Step 1
#Configuration
#EnableTransactionManagement
public class DataSourceConfig {
#Primary
#Bean
#ConfigurationProperties(prefix = "datasource.runtime")
public DataSource runtimeDataSource() {
return DataSourceBuilder.create().build();
}
}
Step 2
#Configuration
#Profile(Profiles.INTEGRATION_TEST)
public class DataSourceTestConfig {
#Autowired
private ResourceLoader resourceLoader;
#Bean
public DataSourceInitializer runtimeDataSourceInitializer(#Qualifier("runtimeDataSource") DataSource dataSource) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(new ResourceDatabasePopulator(
resourceLoader.getResource("classpath:runtime/schema.sql")
));
return initializer;
}
}
Step 3
#Configuration
#EnableTransactionManagement
public class JpaConfig {
#Autowired
private Environment environment;
#Autowired
#Qualifier(value = "runtimeDataSource")
private DataSource runtimeDataSource;
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean runtimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(runtimeDataSource)
.properties(hibernateSettings())
.packages(
"cz.adx.anx.car.cases.domain",
"cz.adx.anx.car.lib.domain",
"org.springframework.data.jpa.convert.threeten" // Hibernate support for Java 8 date and time classes
)
.persistenceUnit("runtimePersistenceUnit")
.build();
}
}
I need beans from class DataSourceTestConfig get initialized before JpaConfig and after DataSourceConfig but only in test mode. In non-test mode beans from JpaConfig should be initialized after DataSourceConfig and beans from DataSourceTestConfig must be omited. Therefore I cannot annotate class JpaConfig with #DependsOn beans from class DataSourceTestConfig because this class is located in test packages and not present in non-test mode. I could duplicate config classes and make them conditional on profile but I don't feel comfortable with this solution. Please, is there a better solution? Thanks in advance!
PS: My app uses two databases/datasources but I shortened the code above to make it easier to read. I'm using Spring Boot 1.3.1.RELEASE.
UPDATE 1:
I tried to use approach suggested by #luboskrnac. I placed annotation ActiveProfiles on my integration test classes:
#ActiveProfiles("IT")
public abstract class IntegrationTest {...}
And I used annotation Profile on relevant beans in class JpaConfig shown below:
#Configuration
#EnableTransactionManagement
public class JpaConfig {
#Autowired
private Environment environment;
#Autowired
#Qualifier(value = "runtimeDataSource")
private DataSource runtimeDataSource;
#Autowired
#Qualifier(value = "configDataSource")
private DataSource configDataSource;
#Profile("!IT")
#Bean(name = "runtimeEntityManagerFactory")
#DependsOn("runtimeDataSource")
public LocalContainerEntityManagerFactoryBean runtimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return createRuntimeEntityManagerFactory(builder);
}
#Profile("IT")
#Bean(name = "runtimeEntityManagerFactory")
#DependsOn("runtimeDataSourceInitializer")
public LocalContainerEntityManagerFactoryBean testRuntimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return jpaConfig.createRuntimeEntityManagerFactory(builder);
}
public LocalContainerEntityManagerFactoryBean createRuntimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(runtimeDataSource)
.properties(hibernateSettings())
.packages(
"cz.adx.anx.car.cases.domain",
"cz.adx.anx.car.lib.domain",
"org.springframework.data.jpa.convert.threeten" // Hibernate support for Java 8 date and time classes
)
.persistenceUnit("runtimePersistenceUnit")
.build();
}
}
And I'm creating the transaction managers the same way. Because I use two datasources (two different databases) I use bean names in the EnableJpaRepositories annotation.
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "runtimeEntityManagerFactory",
transactionManagerRef = "runtimeTransactionManager",
basePackages = "cz.adx.anx.car.lib.repository"
)
public class JpaCarLibRepositoryConfig {
}
So I need the non-test bean and test bean registered under the same name. But Spring gives me an exception:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: runtimeEntityManagerFactory
Any advices please?
I would suggest to drop any considerations about explicit bean creation ordering or bean dependencies.
Simply populate database in test based on Spring #Sql annotation. Test may look something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#Sql("/test-schema.sql")
public class DatabaseTests {
#Test
public void emptySchemaTest {
// execute code that uses the test schema without any test data
}
#Test
#Sql({"/test-schema.sql", "/test-user-data.sql"})
public void userTest {
// execute code that uses the test schema and test data
}
}
If you'll need to swap datasource (e.g. using PostgereSQL in PROD and H2 in tests), just use Spring #Profile, #ActiveProfiles annotations.

spring cloud config client not injecting properties when MyBatis #MapperScan is present

I have an spring-boot application with 3 #Configuration annotated classes: Application, DBScannerConfig and ElasticSearchConfiguration. My application also consumes configuration from a Spring Cloud Config Server.
The problem is that, when I try to annotate attributes with #Value("${key}") inside the DBScannerConfig class, the properties don't get injected. However, if I use the #Value("${key}") annotation in the other two classes, the configuration works well.
I actually think it could be related to the #MapperScan annotation present in my DBScannerConfig configuration class.
What I would like to do is to either be able to retrieve properties or autowire spring's datasource in my #MapperScan annotated class, but I'm not being able to achieve such thing.
Does anyone faced this problem before? Here's the code:
Application.java
#SpringBootApplication
#EnableElasticsearchRepositories("org.myapp.elastic.repository")
#ComponentScan(basePackages = {"org.myapp"})
public class Application extends WebSecurityConfigurerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
// #Autowired
// DataSource dataSource;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
//Other Stuff for Spring Security
}
DBScannerConfig.java
#Configuration
#MapperScan("org.myapp.persistence.mapper")
public class DBScannerConfig{
#Autowired
DataSource dataSource;
#Bean(name = "mySqlSessionFactory")
public SqlSessionFactory mySqlSessionFactory() {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage("org.myapp.persistence.entity");
SqlSessionFactory sqlSessionFactory = null;
try {
sqlSessionFactory = sessionFactory.getObject();
} catch (Exception e) {
LOGGER.error("Error creating mySqlSessionFactory", e);
}
return sqlSessionFactory;
}
}
If I uncomment the #Autowired datasource in Application.java I get a valid object. However, in the DBScannerConfig.java what I get is NULL.
I tried to autowire the Datasource object inside my Application.java class and then use it in the DBSCannerConfig.java with no luck. As soon as I add the #MapperScan annotation into the Application.java class it stops autowiring the spring datasource.

Categories