Java code changeset in liquibase
Yes, there is such feature. You can create a customChange
:
<customChange class="my.java.Class">
<param name="id" value="2" />
</customChange>
The class must implements the liquibase.change.custom.CustomTaskChange
interface.
@Override
public void execute(final Database arg0) throws CustomChangeException {
JdbcConnection dbConn = (JdbcConnection) arg0.getConnection();
try {
... do funny stuff ...
} catch (Exception e) {
// swallow the exception !
}
}
A complete example will look like this
Create a class that implements CustomTaskChange or CustomSqlChange.
package com.example;
import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.CustomChangeException;
import liquibase.exception.SetupException;
import liquibase.exception.ValidationErrors;
import liquibase.logging.LogFactory;
import liquibase.resource.ResourceAccessor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class DataLoaderTask implements CustomTaskChange {
//to hold the parameter value
private String file;
private ResourceAccessor resourceAccessor;
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
@Override
public void execute(Database database) throws CustomChangeException {
JdbcConnection databaseConnection = (JdbcConnection) database.getConnection();
try {
//Opening my data file
BufferedReader in = new BufferedReader(
new InputStreamReader(resourceAccessor.getResourceAsStream(file)));
//Ignore header
String str = in.readLine();
while ((str = in.readLine()) != null && !str.trim().equals("")) {
LogFactory.getLogger().info("Processing line "+ str);
//Do whatever is necessary
}
in.close();
} catch (Exception e) {
throw new CustomChangeException(e);
}
}
@Override
public String getConfirmationMessage() {
return null;
}
@Override
public void setUp() throws SetupException {
}
@Override
public void setFileOpener(ResourceAccessor resourceAccessor) {
this.resourceAccessor = resourceAccessor;
}
@Override
public ValidationErrors validate(Database database) {
return null;
}
}
In the changeset xml you could use the class as below
<changeSet id="1" author="murali" runAlways="false" failOnError="true" >
<customChange class="com.example.DataLoaderTask">
<param name="file" value="/com/example/data/user.csv" />
</customChange>
</changeSet>
For me the data file is in src/main/resources/com/example/data directory
Hope this helps
While both answers were very helpful to me, none of them was complete regarding my use case of manipulating data in the database via a database query. I think when you want to use java code in your changeset (instead of plain SQL code) the real point is you want to read and write something from your database (and in between manipulate it in your java code).
In my case I wanted to encrypt all mail addresses stored in our database. I don't know if the following example is perfect but it does its job pretty well:
Part one: add a changelog
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
<changeSet id="1" author="IssueNo321" runAlways="false" failOnError="true">
<customChange class="class.package.name.EncryptAllMailAddresses"/>
</changeSet>
</databaseChangeLog>
Part two: add the changelog to your changelog list db.changelog-master.yaml
databaseChangeLog:
- includeAll:
relativeToChangelogFile: true
path: changelogs/IssueNo321
Part three: implement migration java code
package class.package.name;
import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.CustomChangeException;
import liquibase.exception.SetupException;
import liquibase.exception.ValidationErrors;
import liquibase.resource.ResourceAccessor;
public class EncryptAllMailAddresses implements CustomTaskChange {
private EncryptionService encryptionService;
@Override
public void execute(Database database) throws CustomChangeException {
try {
var dbConn = (JdbcConnection) database.getConnection(); // autocommit is false
try {
var selectStatement = dbConn.createStatement();
var updateStatement = dbConn.createStatement();
var rs = selectStatement.executeQuery("Select id, email from emails");
while (rs.next()) {
var id = rs.getString("id");
var email = rs.getString("email");
email = encryptionService.encrypt(email);
var updateSql = String.format("update emails set email = '%s' where id = %s", email, id);
updateStatement.execute(updateSql);
}
selectStatement.close();
updateStatement.close();
} catch (Exception e) {
throw new CustomChangeException(e);
}
}
@Override
public String getConfirmationMessage() {
return null;
}
@Override
public void setUp() throws SetupException {}
@Override
public ValidationErrors validate(Database database) {
return null;
}
@Override
public void setFileOpener(ResourceAccessor resourceAccessor) {
// do nothing since we don't need additional resources
}
}