Why isn't the configuration from my application-test.properties being used? - spring

I am trying to run tests in my Spring application which need to use an embedded h2 database (instead of the sql database I use for the actual application). The problem I was facing was that it is executing all queries on the h2 database in upper case and it was failing because of that.
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MOOD" not found; SQL statement:
insert into mood
Now I figured out in application properties you can just put DATABASE_TO_LOWER=TRUE in the connection string, but this is not working for me. I can clearly see in the output that is not using the url I am defining in application-test.properties:
Starting embedded database: url='jdbc:h2:mem:85afe142-af28-4c3a-8226-0a77abdb004d;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
So the relevant files:
my application-test.properties:
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY-2;DATABASE_TO_LOWER=TRUE
spring.datasource.username=sa
spring.datasource.password=sa
my test class:
package com.example.demo.persistence.interfaces;
import com.example.demo.persistence.dto.Mood;
import com.example.demo.persistence.dto.MoodType;
import com.example.demo.persistence.dto.Patient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import java.util.Calendar;
import java.util.Collection;
import static org.junit.jupiter.api.Assertions.*;
#DataJpaTest
#ActiveProfiles("test")
#TestPropertySource(locations="classpath:application-test.properties")
#EnableConfigurationProperties
class MoodRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private MoodRepository repository;
#Autowired
private PatientRepository patientRepository;
#Test
void getMoodsByPatient() {
final Patient patient = new Patient("Kees", "spek", "straat", Calendar.getInstance().getTime());
final Patient patient2 = new Patient("Kees2", "spek2", "straat2", Calendar.getInstance().getTime());
final Mood mood1 = new Mood(patient, MoodType.GOOD,"","","","","","","",Calendar.getInstance().getTime());
final Mood mood2 = new Mood(patient, MoodType.GOOD,"","","","","","","",Calendar.getInstance().getTime());
final Mood mood3 = new Mood(patient2, MoodType.GOOD,"","","","","","","",Calendar.getInstance().getTime());
entityManager.persist(patient);
entityManager.persist(patient2);
entityManager.persist(mood1);
entityManager.persist(mood2);
entityManager.persist(mood3);
final int expectedMoodsFound = 2;
final int actualMoodsFound = ((Collection<?>)repository.getMoodsByPatient(patient)).size();
assertEquals(expectedMoodsFound,actualMoodsFound);
}
#Test
void getMoodByMoodId() {
}
#Test
void deleteAllByDateBefore() {
}
}
So my problem is, I can't seem to properly load in the application-test.properties I guess? What am I doing wrong? I read so many different things on stackoverflow and other websites and none of them worked and I feel like it's a fairly simple issue.

The #DataJpaTest annotation uses the meta annotation #AutoConfigureTestDatabase which has the default configuration to replace any manually configured or auto-configured datasource:
public #interface AutoConfigureTestDatabase {
/**
* Determines what type of existing DataSource bean can be replaced.
* #return the type of existing DataSource to replace
*/
#PropertyMapping(skip = SkipPropertyMapping.ON_DEFAULT_VALUE)
Replace replace() default Replace.ANY;
/**
* What the test database can replace.
*/
enum Replace {
/**
* Replace the DataSource bean whether it was auto-configured or manually defined.
*/
ANY,
/**
* Only replace the DataSource if it was auto-configured.
*/
AUTO_CONFIGURED,
/**
* Don't replace the application default DataSource.
*/
NONE
}
}
If you want to define the database configuration on your own, you can opt-out using:
// ... existing annotations
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MoodRepositoryTest {
// ...
}

Related

How to make Spring IoC container available through out project

I feel stupid to even ask for this but I spent days looking for the answer and I'm still with nothing.
I wanna include simple Spring IoC container in my project. All I want it to do is to allow me Injecting/Autowiring some reusable objects in other classes. What I've done so far looks like this:
-> Project structure here <-
Configuration code:
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.Random;
#Configuration
#ComponentScan(basePackages = "com.example")
public class AppConfig {
#Bean
public Random rand() {
return new Random(42);
}
#Bean
public String string() {
return "Hello World!";
}
}
Main class code:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class Main {
#Autowired
Random rand;
#Autowired
String string;
public static void main(String[] args) {
// workflow
Main main = new Main();
System.out.println(main.string);
}
}
AnotherClass code:
package com.example.deeperpackage;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class AnotherClass {
#Autowired
Random rand;
#Autowired
String string;
public void methodToBeCalled() {
// TODO
System.out.println(string);
}
}
How can I make these #Autowired annotations work? Do I have to instantiate container in every single class in which I want to autowire components? I've seen in work a oracle app which used Spring and #Inject to distribute objects to numerous classes and there was no container logic in any class available for me. Just fields with #Inject annotation. How to achieve that?
Simply add the annotation #Component on the classes you want to inject :
#Component
public class AnotherClass {
...
}
But you cannot inject static attributes and when you do new Main(), no Spring context is being created. If you use Spring Boot, you should look at how to write a main with it.
https://spring.io/guides/gs/spring-boot/

spring boot application showing ??? characters instead of unicode

While reading from database the logs in my dao class shows junk characters in place of unicode characters. Sql developer shows correct values from oracle database also correct NLS language encoding is set on the database.
Below code works for standard jdbc:
connection = DriverManager.getConnection(
"jdbc:oracle:thin:#localhost:1521:xe", "adminuser", "oracle");
Statement st=connection.createStatement();
ResultSet res=st.executeQuery("SELECT menu_item_name from pending_menu_item
where menu_item_id=6062");
while(res.next()){
System.out.println("itemName: "+res.getString(1));
}
Below is the url for springboot project which shows junk characters, I
uploaded to git hub.https://github.com/AyubOpen/spring-boot-jdbc/
package com.mkyong;
import com.mkyong.dao.CustomerRepository;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.util.List;
import static java.lang.System.exit;
#SpringBootApplication
public class SpringBootConsoleApplication implements CommandLineRunner {
#Autowired
DataSource dataSource;
#Autowired
private CustomerRepository customerRepository;
#Autowired
private JdbcTemplate jdbcTemplate;
public static void main(String[] args) throws Exception {
SpringApplication.run(SpringBootConsoleApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
// If you want to check the HikariDataSource settings
HikariDataSource newds = (HikariDataSource)dataSource;
System.out.println("getMaximumPoolSize = " + ((HikariDataSource)
dataSource).getMaximumPoolSize());
System.out.println("DATASOURCE = " +
newds.getDataSourceProperties().getProperty("hikari.useUnicode"));
if (args.length <= 0) {
System.err.println("[Usage] java xxx.jar {display}");
} else {
if (args[0].equalsIgnoreCase("display")) {
System.out.println("Display items...");
List<String> list = customerRepository.findAll();
list.forEach(x -> System.out.println(x));
}
System.out.println("Done!");
}
exit(0);
}
}
package com.mkyong.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
public class CustomerRepository {
#Autowired
private JdbcTemplate jdbcTemplate;
public List<String> findAll() {
List<String> result = jdbcTemplate.query(
"SELECT menu_item_name from pending_menu_item where
menu_item_id=6062",
(rs, rowNum) -> rs.getString("menu_item_name")
);
return result;
}
}
application.properties
----------------------
spring.main.banner-mode=off
spring.datasource.initialize=true
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.url=jdbc:oracle:thin:#localhost:1521/xe
spring.datasource.username=jahezdbapp
spring.datasource.password=oracle
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.hikari.useUnicode=true
spring.datasource.hikari.characterEncoding=utf-8
spring.datasource.hikari.characterSetResults=utf8
# HikariCP settings
#60 sec
spring.datasource.hikari.connection-timeout=60000
# max 5
spring.datasource.hikari.maximum-pool-size=5
Add ?useUnicode=yes&characterEncoding=UTF-8 at the end of spring.datasource.url.
Set spring.datasource.sqlScriptEncoding=UTF-8 in application.properties
1 itself should solve the issue, 2 may not be necessary.
Especially for Oracle. At least I was in the same situation.
It seems, your database uses some national (non-unicode) encoding. So JDBC cannot translate it to UNICODE.
Oracle JDBC by default supports only few character sets: US7ASCII, WE8DEC, WE8ISO8859P1, WE8MSWIN1252, and UTF8. So all strings encoded in different encodings will be displayed as questions. To add support of all other character sets add to the application classpath the file orai18n.jar.
See more here: https://docs.oracle.com/database/121/JJDBC/global.htm#JJDBC28643
This issue does not seem to be with jdbc datasource, I will create a simple github project for springboot and followup on this issue with the below question:
spring boot commandline runner is using windows default character encoding
Record:
worked url for DB2, jdb
jdbc:db2://x.x.x.x:5010/xxxx:useUnicode=yes;characterEncoding=UTF-8;
Had the same issue when reading unicodes. Add this line to the .bat file
-Dfile.encoding=UTF8

Spring aop doesn't run when project starts

I'v implemented a spring-boot aop demo and it runs well, but when I want to use it to load some resource when the project starts, it doesn't work somehow
Aop:
package com.neo.mysql;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* Created by li_weia on 2017/7/6.
*/
#Aspect
#Component
public class DynamicDataSourceAspect {
#Before("#annotation(VendorSource)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在#DS注解
if (method.isAnnotationPresent(VendorSource.class)) {
VendorSource annotation = method.getAnnotation(VendorSource.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
#After("#annotation(VendorSource)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
The VendorSource annotation:
package com.neo.mysql;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by li_weia on 2017/7/6.
*/
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface VendorSource {
String value() default "vendor-master";
}
It runs well here, I can successfully change datasource by annotation:
package com.neo.web;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
public class UserController {
private final ClassMappingDao siteMapper;
#Autowired(required = false)
public UserController(ClassMappingDao siteMapper) {
this.siteMapper = siteMapper;
}
#RequestMapping("/getSites")
#VendorSource("vendor-read")
public List<SiteEntity> getUsers() {
return siteMapper.getAllSite();
}
}
but it doesn't work here, the aop method is not invoked at all:
package com.neo.component;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* Created by li_weia on 2017/7/7.
*/
#Component
public class TestComponent{
private final ClassMappingDao userMapper;
#Autowired(required = false)
public TestComponent(ClassMappingDao userMapper) {
this.userMapper = userMapper;
init();
}
#VendorSource("vendor-read")
public void init() {
List<SiteEntity> sites = userMapper.getAllSite();
for(SiteEntity site: sites){
System.out.println(site.getSite());
}
}
}
You need to fully qualify the annotation, like so:
#Before("execution(public * *(..)) && #annotation(com.neo.mysql.VendorSource)")
private void whatever() {}
Also, as mentioned in my comment above, you need to have spring-boot-starter-aop on classpath. Maybe you already do, but since you didn't say, it's worth mentioning.
Edit:
I didn't notice the real problem before, I wasn't paying attention.
Spring AOP only triggers if you make calls from another class. This is because Spring needs to be able to intercept the call and run the pointcut. Calling the method from constructor is not going to do anything.
You can do a hackish workaround. Create a #PostConstruct void postConstruct() {} method in your class (not constructor), autowire ApplicationContext, and then do MyClassWithInitMethod myClass = context.getBean(MyClassWithInitMethod.class) in the postConstruct method. Then call the method on myClass, and AOP will kick in.
Frankly, I didn't previously check what you are doing in your pointcut, and it's a terrible idea. When multiple threads run, they are going to overwrite the static context, and create a race-condition that you'll then create another question for. Don't do it! Use the factory pattern instead, and inject the DataSourceFactory in the classes that now have the annotation.

Spring beans are not injected in flyway java based migration

I'm trying to inject component of configuration properties in the flyway migration java code but it always null.
I'm using spring boot with Flyway.
#Component
#ConfigurationProperties(prefix = "code")
public class CodesProp {
private String codePath;
}
Then inside Flyway migration code, trying to autowrire this component as following:
public class V1_4__Migrate_codes_metadata implements SpringJdbcMigration {
#Autowired
private CodesProp codesProp ;
public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
codesProp.getCodePath();
}
Here, codesProp is always null.
Is there any way to inject spring beans inside flyway or make it initialized before flyway bean?
Thank You.
Flyway doesn't support dependency injection into SpringJdbcMigration implementations. It simply looks for classes on the classpath that implement SpringJdbcMigration and creates a new instance using the default constructor. This is performed in SpringJdbcMigrationResolver. When the migration is executed, SpringJdbcMigrationExecutor creates a new JdbcTemplate and then calls your migration implementation's migrate method.
If you really need dependencies to be injected into your Java-based migrations, I think you'll have to implement your own MigrationResolver that retrieves beans of a particular type from the application context and creates and returns a ResolvedMigration instance for each.
If like me, you don't want to wait for Flyway 4.1, you can use Flyway 4.0 and add the following to your Spring Boot application:
1) Create a ApplicationContextAwareSpringJdbcMigrationResolver class in your project:
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationType;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
import org.flywaydb.core.api.migration.MigrationChecksumProvider;
import org.flywaydb.core.api.migration.MigrationInfoProvider;
import org.flywaydb.core.api.migration.spring.SpringJdbcMigration;
import org.flywaydb.core.api.resolver.ResolvedMigration;
import org.flywaydb.core.internal.resolver.MigrationInfoHelper;
import org.flywaydb.core.internal.resolver.ResolvedMigrationComparator;
import org.flywaydb.core.internal.resolver.ResolvedMigrationImpl;
import org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationExecutor;
import org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationResolver;
import org.flywaydb.core.internal.util.ClassUtils;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.Pair;
import org.flywaydb.core.internal.util.StringUtils;
import org.flywaydb.core.internal.util.scanner.Scanner;
import org.springframework.context.ApplicationContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* Migration resolver for {#link SpringJdbcMigration}s which are registered in the given {#link ApplicationContext}.
* This resolver provides the ability to use other beans registered in the {#link ApplicationContext} and reference
* them via Spring's dependency injection facility inside the {#link SpringJdbcMigration}s.
*/
public class ApplicationContextAwareSpringJdbcMigrationResolver extends SpringJdbcMigrationResolver {
private final ApplicationContext applicationContext;
public ApplicationContextAwareSpringJdbcMigrationResolver(Scanner scanner, Location location, FlywayConfiguration configuration, ApplicationContext applicationContext) {
super(scanner, location, configuration);
this.applicationContext = applicationContext;
}
#SuppressWarnings("unchecked")
#Override
public Collection<ResolvedMigration> resolveMigrations() {
// get all beans of type SpringJdbcMigration from the application context
Map<String, SpringJdbcMigration> springJdbcMigrationBeans =
(Map<String, SpringJdbcMigration>) this.applicationContext.getBeansOfType(SpringJdbcMigration.class);
ArrayList<ResolvedMigration> resolvedMigrations = new ArrayList<ResolvedMigration>();
// resolve the migration and populate it with the migration info
for (SpringJdbcMigration springJdbcMigrationBean : springJdbcMigrationBeans.values()) {
ResolvedMigrationImpl resolvedMigration = extractMigrationInfo(springJdbcMigrationBean);
resolvedMigration.setPhysicalLocation(ClassUtils.getLocationOnDisk(springJdbcMigrationBean.getClass()));
resolvedMigration.setExecutor(new SpringJdbcMigrationExecutor(springJdbcMigrationBean));
resolvedMigrations.add(resolvedMigration);
}
Collections.sort(resolvedMigrations, new ResolvedMigrationComparator());
return resolvedMigrations;
}
ResolvedMigrationImpl extractMigrationInfo(SpringJdbcMigration springJdbcMigration) {
Integer checksum = null;
if (springJdbcMigration instanceof MigrationChecksumProvider) {
MigrationChecksumProvider version = (MigrationChecksumProvider) springJdbcMigration;
checksum = version.getChecksum();
}
String description;
MigrationVersion version1;
if (springJdbcMigration instanceof MigrationInfoProvider) {
MigrationInfoProvider resolvedMigration = (MigrationInfoProvider) springJdbcMigration;
version1 = resolvedMigration.getVersion();
description = resolvedMigration.getDescription();
if (!StringUtils.hasText(description)) {
throw new FlywayException("Missing description for migration " + version1);
}
} else {
String resolvedMigration1 = ClassUtils.getShortName(springJdbcMigration.getClass());
if (!resolvedMigration1.startsWith("V") && !resolvedMigration1.startsWith("R")) {
throw new FlywayException("Invalid Jdbc migration class name: " + springJdbcMigration.getClass()
.getName() + " => ensure it starts with V or R," + " or implement org.flywaydb.core.api.migration.MigrationInfoProvider for non-default naming");
}
String prefix = resolvedMigration1.substring(0, 1);
Pair info = MigrationInfoHelper.extractVersionAndDescription(resolvedMigration1, prefix, "__", "");
version1 = (MigrationVersion) info.getLeft();
description = (String) info.getRight();
}
ResolvedMigrationImpl resolvedMigration2 = new ResolvedMigrationImpl();
resolvedMigration2.setVersion(version1);
resolvedMigration2.setDescription(description);
resolvedMigration2.setScript(springJdbcMigration.getClass().getName());
resolvedMigration2.setChecksum(checksum);
resolvedMigration2.setType(MigrationType.SPRING_JDBC);
return resolvedMigration2;
}
}
2) Add a new configuration class to post process the Spring Boot generated Flyway instance:
import org.flywaydb.core.Flyway;
import org.flywaydb.core.internal.dbsupport.DbSupport;
import org.flywaydb.core.internal.dbsupport.h2.H2DbSupport;
import org.flywaydb.core.internal.dbsupport.mysql.MySQLDbSupport;
import com.pegusapps.zebra.infrastructure.repository.flyway.ApplicationContextAwareSpringJdbcMigrationResolver;
import org.flywaydb.core.internal.resolver.sql.SqlMigrationResolver;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.PlaceholderReplacer;
import org.flywaydb.core.internal.util.scanner.Scanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.sql.SQLException;
#Configuration
#ComponentScan("db.migration")
public class FlywayConfiguration {
#Bean
public BeanPostProcessor postProcessFlyway(ApplicationContext context) {
return new BeanPostProcessor() {
#Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
return o;
}
#Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
if (o instanceof Flyway) {
Flyway flyway = (Flyway) o;
flyway.setSkipDefaultResolvers(true);
ApplicationContextAwareSpringJdbcMigrationResolver resolver = new ApplicationContextAwareSpringJdbcMigrationResolver(
new Scanner(Thread.currentThread().getContextClassLoader()),
new Location("classpath:db/migration"),
context.getBean(org.flywaydb.core.api.configuration.FlywayConfiguration.class),
context);
SqlMigrationResolver sqlMigrationResolver = null;
try {
sqlMigrationResolver = new SqlMigrationResolver(
getDbSupport(),
new Scanner(Thread.currentThread().getContextClassLoader()),
new Location("classpath:db/migration"),
PlaceholderReplacer.NO_PLACEHOLDERS,
"UTF-8",
"V",
"R",
"__",
".sql");
} catch (SQLException e) {
e.printStackTrace();
}
flyway.setResolvers(sqlMigrationResolver, resolver);
}
return o;
}
private DbSupport getDbSupport() throws SQLException {
DataSource dataSource = context.getBean(DataSource.class);
if( ((org.apache.tomcat.jdbc.pool.DataSource)dataSource).getDriverClassName().equals("org.h2.Driver"))
{
return new H2DbSupport(dataSource.getConnection());
}
else
{
return new MySQLDbSupport(dataSource.getConnection());
}
}
};
}
}
Note that I have some hardcoded dependencies on tomcat jdbc pool, h2 and mysql. If you are using something else, you will need to change the code there (If there is anybody that knows how to avoid it, please comment!)
Also note that the #ComponentScan package needs to match with where you will put the Java migration classes.
Also note that I had to add the SqlMigrationResolver back in since I want to support both the SQL and the Java flavor of the migrations.
3) Create a Java class in the db.migrations package that does the actual migration:
#Component
public class V2__add_default_surveys implements SpringJdbcMigration {
private final SurveyRepository surveyRepository;
#Autowired
public V2__add_surveys(SurveyRepository surveyRepository) {
this.surveyRepository = surveyRepository;
}
#Override
public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
surveyRepository.save(...);
}
}
Note that you need to make the class a #Component and it needs to implement the SpringJdbcMigration. In this class, you can use Spring constructor injection for any Spring bean from your context you might need to do the migration(s).
Note: Be sure to disable ddl validation of Hibernate, because the validation seems to run before Flyway runs:
spring.jpa.hibernate.ddl-auto=none
In short do not autowire beans in your db migrations or even reference classes from your application!
If you refactor/delete/change classes you referenced in the migration it may not even compile or worse corrupt your migrations.
The overhead of using plain JDBC template for the migrations is not worth the risk.
If you are using deltaspike you can use BeanProvider to get a reference to your Class. Here is a DAO example, but it should work fine with your class too.
Change your DAO code:
public static UserDao getInstance() {
return BeanProvider.getContextualReference(UserDao.class, false, new DaoLiteral());
}
Then in your migration method:
UserDao userdao = UserDao.getInstance();
And there you've got your reference.
(referenced from: Flyway Migration with java)

Create and Unit test an Util class

In my application I need to make advanced search against an equipements database, so i created this function in my service interface:
public List<Equipement> findByCriterias(SearchEquipement searchEquipement) ;
I thought that the best way to implement this function is, since I'm working with Spring MVC and Hibernate, to add a function to my DAO interface:
public List<Equipement> getByCriteria(org.hibernate.Criteria criteria) ;
in order to accomplish her mission the findByCriterias method needs to transform the searchEquipement to a org.hibernate.Criteria, so i decided to create an util class to do this (not complete yet) :
public class ApplicationUtil {
private ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("mmapp/util/application-context-util.xml") ;
private SessionFactory sessionFactory ;
public void setSessionFactory(){
sessionFactory = (SessionFactory) context.getBean("sessionFactory") ;
}
public synchronized Criteria changeSearchEquipementToCriteria(SearchEquipementsearchEquipement) {
setSessionFactory() ;
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Equipement.class) ;
return criteria ;
}
}
the problem is when i tried to unit test this part, i have many troubles.
public class ApplicationUtilUnitTests {
#Test
public void testChangeSearchEquipementToCriteria(){
ApplicationUtil util = new ApplicationUtil() ;
Criteria criteria = util.changeSearchEquipementToCriteria(null) ;
assertNotNull(criteria) ;
}
}
that's after several other tries, now i get this error:
org.hibernate.HibernateException: No Session found for current thread
I believe your answer lies in this post. You're mixing Integration Testing with Unit Testing. By nature of your requirement what you can do is:
Use a mocking API such Mockito, PowerMock, EasyMock, or any one that suits you
Mock Hibernate's fundamental features such as SessionFactory, Session, and Criteria (e.g. this post)
Now, you can perform Unit Testing to verify your method is working properly
Try this...
package com.om39a.spring.training.bean;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
#RunWith(PowerMockRunner.class)
#PrepareForTest(ApplicationUtil.class)
public class ApplicationUtilTest {
private ApplicationUtil applicationUtil;
#Mock
SessionFactory mockSessionFactory;
#Mock
Session mockSession;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
applicationUtil = PowerMockito.spy(new ApplicationUtil());
}
#Test
public void testApplicationUtil() throws Exception {
Whitebox.setInternalState("sessionFactory", mockSession);
PowerMockito.doNothing().when(applicationUtil, "setSessionFactory");
Mockito.when(mockSessionFactory.getCurrentSession()).thenReturn(
mockSession);
applicationUtil.changeSearchEquipementToCriteria(null);
/* ...
* ...
* ...
* Assert statments goes here
* ...
* ...
* ...
*/
}
}

Resources