For some reason there's no documentation on running liquibase inside Java code. I want to generate tables for Unit tests.
How would I run it directly in Java?
e.g.
Liquibase liquibase = new Liquibase()
liquibase.runUpdates() ?
It should be something like (taken from liquibase.integration.spring.SpringLiquibase source):
java.sql.Connection c = YOUR_CONNECTION;
Liquibase liquibase = null;
try {
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(c))
liquibase = new Liquibase(YOUR_CHANGELOG, new FileSystemResourceAccessor(), database);
liquibase.update();
} catch (SQLException e) {
throw new DatabaseException(e);
} finally {
if (c != null) {
try {
c.rollback();
c.close();
} catch (SQLException e) {
//nothing to do
}
}
}
There are multiple implementation of ResourceAccessor depending on how your changelog files should be found.
I found a way to achieve setting up the database using either maven or Java. The above example uses FileSystemResourceAccessor(), which unfortunately makes it so that if you deploy an application which needs to set up a database from the jar itself, then you end up having to extract the jar as a zip as a workaround, since these liquibase files exist only in the jar. This means your jar ultimately isn't portable, and you have to have maven wherever you want to set up the database.
Use this structure:
src/main/resources/liquibase/db.changelog-master.xml
src/main/resources/liquibase/changelogs/...
Your DB changelog master can look like this:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!-- <includeAll path="src/main/resources/liquibase/changelogs"/> -->
<include file="changelogs/my-date.1.sql" relativeToChangelogFile="true"/>
</databaseChangeLog>
You can use this section for your pom.xml, in order to make sure mvn install will also set up your liquibase DB.
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<changeLogFile>liquibase/db.changelog-master.xml</changeLogFile>
<driver>org.postgresql.Driver</driver>
<url>${jdbc.url}</url>
<username>${jdbc.username}</username>
<password>${jdbc.password}</password>
</configuration>
<executions>
<execution>
<phase>process-resources</phase>
<goals>
<goal>update</goal>
</goals>
</execution>
</executions>
</plugin>
Use ClassLoaderResourceAccessor() instead of FileSystemResourceAccessor().
public static void runLiquibase() {
Liquibase liquibase = null;
Connection c = null;
try {
c = DriverManager.getConnection(DataSources.PROPERTIES.getProperty("jdbc.url"),
DataSources.PROPERTIES.getProperty("jdbc.username"),
DataSources.PROPERTIES.getProperty("jdbc.password"));
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(c));
log.info(DataSources.CHANGELOG_MASTER);
liquibase = new Liquibase(DataSources.CHANGELOG_MASTER, new ClassLoaderResourceAccessor(), database);
liquibase.update("main");
} catch (SQLException | LiquibaseException e) {
e.printStackTrace();
throw new NoSuchElementException(e.getMessage());
} finally {
if (c != null) {
try {
c.rollback();
c.close();
} catch (SQLException e) {
//nothing to do
}
}
}
}
You can practice with h2-database in test(path "db/changelog.xml" is main/resources/db/changelog.xml):
import liquibase.Contexts;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
import org.junit.jupiter.api.Test;
import java.sql.DriverManager;
import java.sql.SQLException;
public class LiquidBaseTest {
#Test
public void testExecuteLiquidBaseScripts() throws SQLException, LiquibaseException {
java.sql.Connection connection = DriverManager.getConnection("jdbc:h2:mem:");
try {
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
Liquibase liquibase = new Liquibase("db/changelog.xml", new ClassLoaderResourceAccessor(), database);
liquibase.update(new Contexts());
} finally {
if (connection != null) {
connection.rollback();
connection.close();
}
}
}
}
public static void runLiquibase() throws Exception {
Map<String, Object> config = new HashMap<>();
Scope.child(config, () -> {
try {
Connection connection = DriverManager.getConnection("your database connection url");
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
Liquibase liquibase = new liquibase.Liquibase("database/db.changelog-main.xml", new ClassLoaderResourceAccessor(), database);
liquibase.update(new Contexts(), new LabelExpression());
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
}
This code will help you to manage a multiple changelogs based on the version and you can provide a reference of main file.
I have followed structure as below.
src/main/resources/database/db.changelog-main.xml
src/main/resources/database/changelogs/db.changelog-v-1.0.0.xml
src/main/resources/database/changelogs/db.changelog-v-1.0.1.xml
A reference link https://docs.liquibase.com/workflows/liquibase-community/using-liquibase-java-api.html
Related
I'm currently facing a problem.
My project looks like this :
Project
|_ module 1
|_ liquibase
|_ migration.xml
|_ file1.xml
|_ src
|_ main
|_ java
|_ resources
To be able to launch component tests, I run, using docker, a postgresql container.
I want to launch my liquibase scripts.
Here's a my code :
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setResourceLoader(new FileSystemResourceLoader());
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("liquibase/migration.xml");
liquibase.setDefaultSchema("mySchema");
liquibase.setDropFirst(false);
liquibase.setShouldRun(true);
try {
liquibase.afterPropertiesSet();
log.info("Liquibase run ended");
} catch (Exception e) {
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
This has run well for a long time, until I made an update to Liquibase 4.
Now, I'm getting the following error : Specifying files by absolute path was removed in Liquibase 4.0. Please use a relative path or add '/' to the classpath parameter.
I searched throught the web and didn't find anything helpful.
I tried a lot of different things, and nothing worked
Someone has a clue ? (other than moving my liquibase folder inside resources)
I worked it out implementing custom SpringLiquibase and SpringResourceAcessor and moving from liquibase 4.0 to 4.6.1
If anyone is interested, here's my code :
public class CustomSpringResourceAcessor extends SpringResourceAccessor {
public CustomSpringResourceAcessor(ResourceLoader resourceLoader) {
super(resourceLoader);
}
#Override
protected String finalizeSearchPath(String searchPath) {
return super.finalizeSearchPath(searchPath).substring(11);
}
#Override
public InputStreamList openStreams(String relativeTo, String streamPath) throws IOException {
String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
path = path.substring(0, path.indexOf("/target"));
if (relativeTo == null) {
return super.openStreams(path, streamPath);
}
return super.openStreams(path + "/" + relativeTo, streamPath);
}
}
and
public class CustomSpringLiquibase extends SpringLiquibase {
#Override
protected SpringResourceAccessor createResourceOpener() {
return new CustomSpringResourceAcessor(getResourceLoader());
}
}
On Xampp Tomcat on Windows 11, I am trying to monitor java-web-app with java melody.
However, sql data is not detected by java melody.
Could you figure out what i am missing?
I have created a library project, not to do same settings on every app
Here is the projects code...
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tugalsan</groupId>
<artifactId>api-profile</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.bull.javamelody</groupId>
<artifactId>javamelody-core</artifactId>
<version>1.90.0</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-url</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.java</include>
<include>**/*.gwt.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
<properties>
<maven.compiler.source>15</maven.compiler.source>
<maven.compiler.target>15</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
TGS_ProfileServletUtils.java:
package com.tugalsan.api.profile.client;
import com.tugalsan.api.url.client.parser.*;
public class TGS_ProfileServletUtils {
final public static String SERVLET_NAME = "monitoring";//HARD-CODED IN LIB, THIS CANNOT BE CHANGED!
}
TS_ProfileMelodyUtils.java:
package com.tugalsan.api.profile.server.melody;
import java.sql.*;
import javax.sql.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import net.bull.javamelody.*;
import com.tugalsan.api.profile.client.*;
public class TS_ProfileMelodyUtils {
#WebFilter(
filterName = TGS_ProfileServletUtils.SERVLET_NAME,
dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.ASYNC},
asyncSupported = true,
urlPatterns = {"/*"},
initParams = {
#WebInitParam(name = "async-supported", value = "true")
}
)
final public static class MelodyFilter extends MonitoringFilter {
}
#WebListener
final public static class MelodyListener extends SessionListener {
}
public static Connection createProxy(Connection con) {
try {
DriverManager.registerDriver(new net.bull.javamelody.JdbcDriver());
return JdbcWrapper.SINGLETON.createConnectionProxy(con);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static DataSource createProxy(DataSource ds) {
try {
DriverManager.registerDriver(new net.bull.javamelody.JdbcDriver());
return JdbcWrapper.SINGLETON.createDataSourceProxy(ds);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
a helper class
package com.tugalsan.api.sql.conn.server;
import java.io.Serializable;
import java.util.Objects;
public class TS_SQLConnConfig implements Serializable {
public int method = TS_SQLConnMethodUtils.METHOD_MYSQL();
public String dbName;
public String dbIp = "localhost";
public int dbPort = 3306;
public String dbUser = "root";
public String dbPassword = "";
public boolean autoReconnect = true;
public boolean useSSL = false;
public boolean region_ist = true;
public boolean charsetUTF8 = true;
public boolean isPooled = true;
public TS_SQLConnConfig() {//DTO
}
public TS_SQLConnConfig(CharSequence dbName) {
this.dbName = dbName == null ? null : dbName.toString();
}
}
On another api, this is how i create a pool
(I skipped some class files, unrelated to the question)
public static PoolProperties create(TS_SQLConnConfig config) {
var pool = new PoolProperties();
pool.setUrl(TS_SQLConnURLUtils.create(config));
pool.setDriverClassName(TS_SQLConnMethodUtils.getDriver(config));
if (TGS_StringUtils.isPresent(config.dbUser) && TGS_StringUtils.isPresent(config.dbPassword)) {
pool.setUsername(config.dbUser);
pool.setPassword(config.dbPassword);
}
var maxActive = 200;
pool.setMaxActive(maxActive);
pool.setInitialSize(maxActive / 10);
pool.setJmxEnabled(true);
pool.setTestWhileIdle(true);
pool.setTestOnBorrow(true);
pool.setTestOnReturn(false);
pool.setValidationQuery("SELECT 1");
pool.setValidationInterval(30000);
pool.setTimeBetweenEvictionRunsMillis(30000);
pool.setMaxWait(10000);
pool.setMinEvictableIdleTimeMillis(30000);
pool.setMinIdle(10);
pool.setFairQueue(true);
pool.setLogAbandoned(true);
pool.setRemoveAbandonedTimeout(600);
pool.setRemoveAbandoned(true);
pool.setJdbcInterceptors(
"org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"
+ "org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer;"
+ "org.apache.tomcat.jdbc.pool.interceptor.ResetAbandonedTimer");
return pool;
}
WAY1:
//I created datasource once, save it as a global variable inside a ConcurrentLinkedQueue.
var pool_ds = new DataSource(create(config));
//then for every connection need, i created an extra proxy like this.
var pool_con = pool_ds.getConnection();
var proxy_con = TS_ProfileMelodyUtils.createProxy(pool_con);
//and close both of them later on
WAY1 RESULT:
WAY2:
//I created datasource once, save it as a global variable inside a ConcurrentLinkedQueue.
var pool_ds = new DataSource(create(config));
//then i created a proxy datasource, save it as a global variable too
var dsProxy = TS_ProfileMelodyUtils.createProxy(ds);
//then for every connection need, i did not create a proxy connection.
var pool_con = pool_ds.getConnection();
//and close connection later on
WAY2 RESULT: (same, nothing changed)
WAY3:
//I created datasource once, save it as a global variable inside a ConcurrentLinkedQueue.
var pool_ds = new DataSource(create(config));
//then i created a proxy datasource, save it as a global variable too
var dsProxy = TS_ProfileMelodyUtils.createProxy(ds);
//then for every connection need, i created an extra proxy like this.
var pool_con = pool_ds.getConnection();
var proxy_con = TS_ProfileMelodyUtils.createProxy(pool_con);
//and close both of them later on
WAY3 RESULT: (same, nothing changed)
I think i found the problem.
One should create connection from proxy_datasource not pool_datasource
var pool_con = pool_ds.getConnection(); //WRONG
var pool_con = proxy_ds.getConnection(); //RIGHT
And also, on creating statements,
one should use proxy connections (proxy_con) to create statements, not main connection (pool_con)!
I used way 3. And the results were singleton. I think java melody detects that it has a datasource already; and does not log twice.
full code: at github-profile
full code: at github-sql-conn
I have written an AWS Lambda Handler as below :
package com.lambda;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import java.io.*;
public class TestDetailsHandler implements RequestStreamHandler {
public void handleRequest(InputStream input,OutputStream output,Context context){
// Get Lambda Logger
LambdaLogger logger = context.getLogger();
// Receive the input from Inputstream throw exception if any
File starting = new File(System.getProperty("user.dir"));
System.out.println("Source Location" + starting);
File cityFile = new File(starting + "City.db");
FileInputStream fis = null;
try {
fis = new FileInputStream(cityFile);
System.out.println("Total file size to read (in bytes) : "
+ fis.available());
int content;
while ((content = fis.read()) != -1) {
// convert to char and display it
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null)
fis.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
Its read a file : City.db , available in resources folder, even I kept to everywhere see below :
But it showing following message on execution of this lambda function :
START RequestId: 5216ea47-fc43-11e5-96d5-83c1dcdad75d Version: $LATEST
Source Location/
java.io.FileNotFoundException: /city.db (No such file or directory)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at com.lambda.TestDetailsHandler.handleRequest(TestDetailsHandler.java:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at lambdainternal.EventHandlerLoader$StreamMethodRequestHandler.handleRequest(EventHandlerLoader.java:511)
at lambdainternal.EventHandlerLoader$2.call(EventHandlerLoader.java:972)
at lambdainternal.AWSLambda.startRuntime(AWSLambda.java:231)
at lambdainternal.AWSLambda.<clinit>(AWSLambda.java:59)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at lambdainternal.LambdaRTEntry.main(LambdaRTEntry.java:93)
END RequestId: 5216ea47-fc43-11e5-96d5-83c1dcdad75d
REPORT RequestId: 5216ea47-fc43-11e5-96d5-83c1dcdad75d Duration: 58.02 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 50 MB
Contents of the Pom.xml file :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lambda</groupId>
<artifactId>testdetails</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>test-handler</name>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
I have used various ways to keep file here and there , but at the end its not working. May you please let me know what is wrong here ?
However in my another project where I have kept xyz.properties file in resources folder and reading from a PropertyManager file, its working fine. When I tested it on my system its working fine, but on AWS Lambda function it doesn't work.
I have made following changes in my code and now its works perfect :
Majorly changed following two lines :
ClassLoader classLoader = getClass().getClassLoader();
File cityFile = new File(classLoader.getResource("City.db").getFile());
package com.lambda;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import java.io.*;
public class TestDetailsHandler implements RequestStreamHandler {
public void handleRequest(InputStream input,OutputStream output,Context context){
// Get Lambda Logger
LambdaLogger logger = context.getLogger();
// Receive the input from Inputstream throw exception if any
ClassLoader classLoader = getClass().getClassLoader();
File cityFile = new File(classLoader.getResource("City.db").getFile());
FileInputStream fis = null;
try {
fis = new FileInputStream(cityFile);
System.out.println("Total file size to read (in bytes) : "
+ fis.available());
int content;
while ((content = fis.read()) != -1) {
// convert to char and display it
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null)
fis.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
This is how I did it, let's say this is how your project structure looks like -
And you want to read the file config.properties which is inside the project-dir/resources directory.
The code for reading the content of the file would be -
InputStream input = null;
try {
Path path = Paths.get(PropertyUtility.class.getResource("/").toURI());
// The path for config file in Lambda Instance -
String resourceLoc = path + "/resources/config.properties";
input = new FileInputStream(resourceLoc);
} catch(Exception e) {
// Do whatever
}
If you are following this project structure and using this code, then it will work in AWS Lambda.
PropertyUtility is just a utility class that I have created to read the contents of the config file. The PropertyUtility class looks like this -
As you can see in the above code, the path of the config file is different in the local system and in Lambda Instance.
In your local machine, PropertyUtility.class.getResource("/") points to bin, that is why you have to do path.getParent(), to point it to the project-directory which is HelloLambda in this example.
For the Lambda Instance, PropertyUtility.class.getResource("/") points directly to the project-directory.
If the file in located under resources directory, then the following solution should work:
String fileName = "resources/config.json";
Path path = Paths.get(this.getClass().getResource("/").toURI());
Path resourceLocation = path.resolve(fileName);
try(InputStream configStream = Files.newInputStream(resourceLocation)) {
//use your file stream as you need.
}
Here the most important part is "resources/config.json", it must not be "/resources/config.json", because the file location is /var/task/resources/config.json in lambda, I checked.
Hope this helps who still face problem in reading file in aws lambda.
If the file is located under resources folder, you can use it directly in lambda by using something like the following code:
final BufferedReader br = new BufferedReader(new FileReader("/flows/cancellation/MessageArray.json"));
I wanted to read a json file, you can have different use case, but the code works.
Ideally, one should read read from S3 as much as possible to have dynamic reads. Plus the reads are pretty fast.
However, as if your Java code is Maven based, your root classpath location starts from src/main/resources location.
So you can read, as you read in any web/core app, from the classpath as given below -
ClassLoader classLoader = YourClass.class.getClassLoader();
File cityFile = new
File(classLoader.getResource("yourFile").getFile());
This has worked for me pretty well!
I have dropwizard-application (0.7.0) for which I want to run integration tests.
I've set up an integration test using DropwizardAppRule, like this:
#ClassRule
public static final DropwizardAppRule<MyAppConfiguration> RULE =
new DropwizardAppRule<MyAppConfiguration>(
MyApplication.class, Resources.getResource("testconfiguration.yml").getPath());
When I try to run the below tests using it, it doesn't work because I haven't run my migrations. What is the best way to run the migrations?
Test:
#Test
public void fooTest() {
Client client = new Client();
String root = String.format("http://localhost:%d/", RULE.getLocalPort());
URI uri = UriBuilder.fromUri(root).path("/users").build();
client.resource(uri).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).post(User.class, new LoginUserDTO("email#email.com", "password"));
}
Configuration:
public class MyAppConfiguration extends Configuration {
#Valid
#NotNull
private DataSourceFactory database = new DataSourceFactory();
#JsonProperty("database")
public DataSourceFactory getDataSourceFactory() {
return database;
}
#JsonProperty("database")
public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
this.database = dataSourceFactory;
}
}
Thanks to Kimble and andersem for putting me on the right track. Here's what I came up with in my #BeforeClass method:
// Create the test database with the LiquiBase migrations.
#BeforeClass
public static void up() throws Exception
{
ManagedDataSource ds = RULE.getConfiguration().getMainDataSource().build(
RULE.getEnvironment().metrics(), "migrations");
try (Connection connection = ds.getConnection())
{
Liquibase migrator = new Liquibase("migrations.xml", new ClassLoaderResourceAccessor(), new JdbcConnection(connection));
migrator.update("");
}
}
I ran into some concurrency issues when trying to do the database migration as part of the test case and ended up baking it into the application itself (protected by a configuration option).
private void migrate(MyAppConfiguration configuration, Environment environment) {
if (configuration.isMigrateSchemaOnStartup()) {
log.info("Running schema migration");
ManagedDataSource dataSource = createMigrationDataSource(configuration, environment);
try (Connection connection = dataSource.getConnection()) {
JdbcConnection conn = new JdbcConnection(connection);
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(conn);
Liquibase liquibase = new Liquibase("migrations.xml", new ClassLoaderResourceAccessor(), database);
liquibase.update("");
log.info("Migration completed!");
}
catch (Exception ex) {
throw new IllegalStateException("Unable to migrate database", ex);
}
finally {
try {
dataSource.stop();
}
catch (Exception ex) {
log.error("Unable to stop data source used to execute schema migration", ex);
}
}
}
else {
log.info("Skipping schema migration");
}
}
private ManagedDataSource createMigrationDataSource(MyAppConfiguration configuration, Environment environment) {
DataSourceFactory dataSourceFactory = configuration.getDataSourceFactory();
try {
return dataSourceFactory.build(environment.metrics(), "migration-ds");
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Unable to initialize data source for schema migration", ex);
}
}
Another approach that doesn't rely on importing Liquibase's classes directly is to run the db migrate command in the same way that you might from the command line, using the RULE:
#Before
public void migrateDatabase() throws Exception {
RULE.getApplication().run("db", "migrate", ResourceHelpers.resourceFilePath("testconfiguration.yml"));
}
This approach also works for any other commands from any other bundles that you might want to run before starting the tests.
A small winkle: Doing this with any commands that extend Dropwizards ConfiguredCommand (which all of the dropwizard-migrations do) will unnecessarily disable logback when the command finishes.
To restore it, you can call:
RULE.getConfiguration().getLoggingFactory().configure(RULE.getEnvironment().metrics(),
RULE.getApplication().getName());
I did it this way using Liquibase's API:
private void migrate(){
DataSourceFactory dataSourceFactory = RULE.getConfiguration().dataSourceFactory;
Properties info = new Properties();
info.setProperty("user", dataSourceFactory.getUser());
info.setProperty("password", dataSourceFactory.getPassword());
org.h2.jdbc.JdbcConnection h2Conn = new org.h2.jdbc.JdbcConnection(dataSourceFactory.getUrl(), info);
JdbcConnection conn = new JdbcConnection(h2Conn);
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(conn);
Liquibase liquibase = new Liquibase("migrations.xml", new ClassLoaderResourceAccessor(), database);
String ctx = null;
liquibase.update(ctx);
}
And then I put this in a beforeclass:
#BeforeClass
public void setup(){
migrate();
}
It's probably not the ultimate solution, and it depends a lot on the database you're using, but it works.
What I do to achieve the same goal is to run the migration from within maven.
Add this to the section in the sction of your pom.xml:
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>3.0.5</version>
<executions>
<execution>
<phase>process-test-resources</phase>
<configuration>
<changeLogFile>PATH TO YOUR MIGRATIONS FILE</changeLogFile>
<driver>org.h2.Driver</driver>
<url>JDBC URL LIKE IN YOUR APP.YML</url>
<username>USERNAME</username>
<password>PASSWORD</password>
<dropFirst>false</dropFirst>
<promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
<logging>info</logging>
</configuration>
<goals>
<goal>dropAll</goal>
<goal>update</goal>
</goals>
</execution>
</executions>
</plugin>
This will work with maven from command line. With this setting, maven will use liquibase dropAll to drop all database objects, and then run a migration, so with every test you have a clean new database.
When using that, I ran intoissues with eclipse, it complained about the lifecycle mapping not working upon the execution tag of the plugin. In this case, you need to add the following to the build section as well, so eclipse can properly map the life cycles:
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<versionRange>[1.0,)</versionRange>
<goals>
<goal>dropAll</goal>
<goal>update</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute />
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
*Please forgive the intricate title*
Background
/pom.xml
...
<foo.bar>stackoverflow</foo.bar>
...
/src/main/resources/config.properties
...
foo.bar=${foo.bar}
...
Config.java
...
public final static String FOO_BAR;
static {
try {
InputStream stream = Config.class.getResourceAsStream("/config.properties");
Properties properties = new Properties();
properties.load(stream);
FOO_BAR = properties.getProperty("foo.bar");
} catch (IOException e) {
e.printStackTrace();
}
}
...
Question
In /src/main/java, I'm using Config.FOO_BAR in MyClass.java. If I want to test MyClass in a /src/test/java folder using JUnit with MyClassTest.java, how can I load the properties so that the Config.FOO_BAR constant get initialized?
I tried to add a hardly-written config.properties within /src/test/resources with foo.bar=stackoverflow, but it still can't get initialized.
I could make it work by changing some in your pom.xml and your Config.java.
Add these lines to your pom.xml:
<project>
...
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
And change the order of some lines in Config.java:
public class Config {
public final static String FOO_BAR;
static {
InputStream stream = Config.class.getResourceAsStream("/config.properties");
Properties properties = new Properties();
try {
properties.load(stream);
} catch (IOException e) {
e.printStackTrace();
// You will have to take some action here...
}
// What if properties was not loaded correctly... You will get null back
FOO_BAR = properties.getProperty("foo.bar");
}
public static void main(String[] args) {
System.out.format("FOO_BAR = %s", FOO_BAR);
}
}
Output if running Config:
FOO_BAR = stackoverflow
Disclaimer
I am not sure what purpose you have with setting these static config values. I just made it work.
Edit after comment
Added a simple JUnit test to src/test/java/:
package com.stackoverflow;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* #author maba, 2012-09-25
*/
public class SimpleTest {
#Test
public void testConfigValue() {
assertEquals("stackoverflow", Config.FOO_BAR);
}
}
No problems with this test.