Play Framework 2.0 and EBean, wrapping INFORMATION_SCHEMA - java

Using Play! Framework 2.0.4 and EBean as the persistence layer I am attempting to wrap a databases "meta" information to Java classes.
I map the classes in application.conf:
db.myData.url="jdbc:sqlserver://localhost:2301;databaseName=myData;"
ebean.myData="models.database.JavaTable"
I then created a class as follows:
package models.database;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import play.db.ebean.Model;
#Entity
#Table(name="tables",schema="INFORMATION_SCHEMA",catalog="myData")
public class JavaTable extends Model{
#Column(name="TABLE_NAME")
public String table_name;
public static Finder<String, JavaTable> find = new Finder<String, JavaTable>(
String.class, JavaTable.class
);
}
When I fire up Play!, it tells me that I need to run an evolution on the database to create the table "myData.INFORMATION_SCHEMA.tables". I then tried to test the connection via a unit test...
#Test
public void testGetTables(){
running(fakeApplication(), new Runnable() {
#Override
public void run() {
EbeanServer server = Ebean.getServer("myData");
List<SqlRow> rows = server.createSqlQuery("select * from myData.Information_Schema.Tables").findList();
for(SqlRow row: rows)
System.out.println("====>Row: " + row.toString());
}
});
}
The unit test executes correctly and the table names are printed out successfully.
Edit: per #nico_ekito I removed the evolution plugin in the configuration file and started getting:
RuntimeException DataSource user is null
So, researching a bit I decided to disable other datasources in the config file and moved the database I'm attempting to communicate with to "db.default" and "ebean.default" and the models started working. Is there a way to tell a model which datasource it should use if Play has multiple databases defined? Setting the classes via "ebean.default="myData.JavaTable" did not seem to work.

You should try to disable the evolutions by adding the following in your application.conf file:
evolutionplugin=disabled
The tests are ok because since you're not starting a real application, but using a fakeApplication() which does not use the evolutions plugin.

Anecdotally, I've only had success defining my eBean server using package level values (so, "myData.*" instead of "myData.JavaTable"). To pull this off, you may have to move all classes for a particular eBean server to their own package.

Related

Quarkus: clean H2 DB after every test

I set up a standard Quarkus project as described in the quickstart, and I'm now running multiple #QuarkusTest, using Liquibase and H2.
I noticed that between tests, the data written in H2 is preserved.
Is my project wrongly configured or does it behave like so for everyone?
Do Quarkus/H2 provide a way to automatically clean all the tables after every test execution?
I don't know if there is a way to "automatically clean all the tables after every test execution".
One workaround I'm doing is to create one class on the test package and put all the DELETEs needed there. The idea is to run this deletion method on the beginning of the tests that need clean tables.
package org.acme.tests;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
#ApplicationScoped
public class CleanTables {
#Inject
EntityManager entityManager;
#Transactional
public void clean() {
entityManager.createNativeQuery("DELETE FROM YOUR_SCHEMA.YOUR_TABLE_A").executeUpdate();
entityManager.createNativeQuery("DELETE FROM YOUR_SCHEMA.YOUR_TABLE_B").executeUpdate();
}
}

Spring Boot Test: execute different sql scripts in tests depending on the active profile?

Is it possible with Spring Boot Test to set a conditional execution of sql scripts depending on the active profile?
I mean, I have my integration tests for repositories annotated with some #sql annotations like:
#Sql(scripts = "/scripts/entity_test_clear.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
For a profile h2 I want to execute entity_test_clear.sql
For a profile mysql I want to execute entity_test_clear_mysql.sql
The reason for it is that I use diffrent syntax for these databases, particularly this one:
ALTER TABLE organisation ALTER COLUMN org_id RESTART WITH 1;
ALTER TABLE organisation AUTO_INCREMENT = 1;
Mysql doesn't understand the syntax #1, while h2 doesn't understand the syntax #2 (despite the mysql mode set, like MODE=MYSQL)
By default, I use h2 for IT tests, but also, in some rarer cases, I would like to check everything works smoothly with mysql too.
P.S I could of course try a straight-forward solution with #Profile and hard code two copies of each test for h2 and mysql, but it is coupled with huge code duplication in tests, which I would like to avoid.
EDITED:
The test case looks like this:
#RunWith(SpringRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
public class EntityRepositoryTestIT {
#Autowired
private EntityRepository entityRepository;
#Test
#Sql(scripts = {"/scripts/entity_test_data.sql", "/scripts/entity_test_data_many.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
#Sql(scripts = "/scripts/entity_test_clear.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void findTest() {
Page<Entity> e = entityRepository.findBySomeDetails(1L, PageRequest.of(0, 20));
Assert.assertEquals(3, e.getContent().size());
Assert.assertEquals(1, e.getContent().get(0).getResources().size());
// more asserts
}
Thank you for any suggestions!
After some deeper digging into the issue I ended up with this simple workaround.
#Sql(scripts = "/scripts/entity_test_clear.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
For scripts parameter, it is required to be a compile time constant. You cannot simply fetch a current profile value from application.properties and substitute it to run the right script name.
Introducing #After and #Before methods with ScriptUtils executing the right scripts is rather verbose and, in fact, didn't work for me (some freezing during script executing process occurred).
So what I did was just introducing a class with a single constant:
/**
* Constant holder for exceptionally database IT testing purposes
* for switching between h2 and mysql
*/
public class ActiveProfile {
/**
* Current profile for database IT tests.
* Make sure the value is equal to the value of
* <i>spring.profiles.active</i> property from test application.properties
*/
public static final String NOW = "h2";
}
Then the #sql line becomes:
#Sql(scripts = "/scripts/test_data_clear_"+ ActiveProfile.NOW+".sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
To use another database for testing (mysql) I just need to 1) change the current spring.profiles.active=mysql profile in application.properties and 2) change this constant to mysql;
That doesn't mean to be the exemplary solution, just a workaround that simply works.
You could use #Profile annotation with separated classes, each for every DMBS, putting the common logic in an another class to avoid the code duplication. You are using Spring so you could get it with something as below.
#Profile("mysql")
#Sql(scripts="... my mysql scripts...")
public class MySqlTests{
#Autowired
private CommonTestsLogic commonLogic;
#Test
public void mySqlTest1(){
commonlogic.test1();
}
}
#Profile("oracle")
#Sql(scripts="... my oracle scripts...")
public class MyOracleTests{
#Autowired
private CommonTestsLogic commonLogic;
#Test
public void myOracleTest1(){
commonlogic.test1();
}
}

Java Play 2.4 write test case for class using injection

Recently I am using play 2.4 framework for Java project.
In that I am using WsClient Library. That library is injected in my class.
#Inject WSClient wsClient
Now I am trying to write a test case for that class but test case fails because of null pointer error for wsClient variable.
wsClient.url("some url").get()
Can you please help me to resolve this?
following is the test code
// Class
public class ElasticSearch {
#Inject WSClient wsClient;
public Promise<WSResponse> createIndex() {
Logger.info("Entering ElasticSearch.createIndex()");
Logger.debug("WSClient: " + wsClient);
Promise<WSResponse> response =wsClient.url(this.getEsClient()+ "/" +this.getEsIndexName()).setContentType("application/json").put("");
Logger.info("Exiting ElasticSearch.createIndex()");
return response;
}
}
// Test function
#Test
public void testCreateIndex() {
running(fakeApplication(), new Runnable() {
public void run() {
ElasticSearch esearch= new ElasticSearch();
esearch.setEsIndexName("car_model");
assertNotNull(esearch.createIndex());
}
});
}
Before writing the options you have, I suggest to use elastic4s.
This third party lib will help you write a more functional code and give you very nice dsl to write your queries.
One more thing, I don't know what is your used case for using elasticsearch, but I recommend using a different client then the rest api, which will give you more secure connection and more efficient.
You get the NPE, because you instasiate ElasticSearch by your own with new, and don't let guice to the wiring for you, this is why the WSClient is null.
Now for your options,
You have 2 options:
Add WithApplication to your test, which will basically load your application, this will give you the access to Guice injector, from which you can take ElasticSearch class like this you have couple of ways to do it:
As described in play documentation using
import play.api.inject.guice.GuiceInjectorBuilder
import play.api.inject.bind
val injector = new GuiceInjectorBuilder()
.configure("key" -> "value")
.bindings(new ComponentModule)
.overrides(bind[Component].to[MockComponent])
.injector
val elasticsearch = injector.instanceOf[ElasticSearch]
By importing Play
import play.api.Play
val elasticsearch = Play.current.injector.instanceOf(classOf[ElasticSearch])
Using FakeApplication: just get hold of the fake application injector, and use it to get an instance of ElasticSearch class.
I don't like the above options, because you need an application running, which can make your tests very slow.
I suggest to create the WSClient by your self and instantiate ElasticSearch class with it, and run your tests.
val httpClient = NingWSClient.apply()
val elasticsearch = new ElasticSearch(httpClient)
This is a more light solution, and should make your tests run faster.

How to load / insert initial data into database with Ebean in Play Framework?

I need to some initial data (from csv files) into database. I am using Ebean with Play! Framework. I read the doc. It says using YAML files to store the data and call Ebean.save(). And this is done in a Test.
My questions:
Where should I insert my data? (Test is probably not the ideal place, as this data should be used in production)
Can I write my own code to get my data from existing csv files, instead of using YAML files?
Any suggestions or doc links will be appreciated.
Thanks!
A Play Framework 2.4 solution to loading initial data on application startup, using Ebean:
First, create a class where you want perform the initial data load. According to the docs this has to be in the constructor.
public class InitialData {
public InitialData() {
if (Ebean.find(User.class).findRowCount() == 0) {
InputStream is = this.getClass().getClassLoader().getResourceAsStream("initial-data.yml");
#SuppressWarnings("unchecked")
Map<String, List<Object>> all =
(Map<String, List<Object>>)Yaml.load(is, this.getClass().getClassLoader());
//Yaml.load("initial-data.yml"); //calls Play.application() which isn't available yet
Ebean.save(all.get("users"));
}
}
}
NOTE: I used Yaml's load(InputStream is, ClassLoader classloader) method over its load(String resourceName) method because it does not work. I believe this is because of its use of Play.application(), as this is not available before the application is started. Source Here
Second, According to Play docs, to execute code before startup, use Eager-bindings. This will cause your code in the InitialData constructor to execute.
public class MainModule extends AbstractModule implements AkkaGuiceSupport {
#Override
protected void configure() {
//this is where the magic happens
bind(InitialData.class).asEagerSingleton();
}
}
Make sure to add your MainModule class to the application.conf
# modules
play.modules.enabled += "module.MainModule"
With further search, I found the solution is described here
The code that perform initial insertion needs to be hooked into Play's startup.
Hooking into Play’s startup is as simple as creating a class called Global that implements GlobalSettings in the root package, and overriding the onStart() method. Let’s do that now, by creating the app/Global.java fileHooking into Play’s startup is as simple as creating a class called Global that implements GlobalSettings in the root package, and overriding the onStart() method. Let’s do that now, by creating the app/Global.java file
Note that, it said the class needs to be in the root package.
code example:
import play.*;
import play.libs.*;
import com.avaje.ebean.Ebean;
import models.*;
import java.util.*;
public class Global extends GlobalSettings {
#Override
public void onStart(Application app) {
// Check if the database is empty
if (User.find.findRowCount() == 0) {
Ebean.save((List) Yaml.load("initial-data.yml")); // You can use whatever data source you have here. No need to be in YAML.
}
}
}

How to configure ebean server via java for Play!2.1.1

Hi i'm having some problem with ebean and play configuration, seems like i can't get the ebean server recognize some classes in my project, i got
[PersistenceException: The type [class model.mappe.NodoSemplice] is not a registered entity? If you don't explicitly list the entity classes to use Ebean will search for them in the classpath. If the entity is in a Jar check the ebean.search.jars property in ebean.properties file or check ServerConfig.addJar().]
where the class model.mappe.NodoSemplice is as such
#Entity
public class NodoSemplice extends NodoApprendimento {
and
#Entity
#Table(name="nodi_app")
public abstract class NodoApprendimento extends Model{
the imports are correct and the ebeanserver configuration is as such
ServerConfig def = new ServerConfig();
def.setName("fluidefault");
def.setDataSource(play.db.DB.getDataSource("fluiddefault"));
//i package delle classi di sistema
def.addPackage("model.contributi.*");
def.addPackage("model.corpi.*");
def.addPackage("model.mappe.*");
def.addPackage("model.utenti.*");
def.setDdlGenerate(true); //per generare il DataDefinitionLanguage
fluidefault = EbeanServerFactory.create(def);
the save was done trough via
public void save(NodoApprendimento nodoApprendimento){
nodoApprendimento.save(fluidefault.getName());
}
the server bootstrap is fine and also talking with it but still i got the persistence exception because the server can't recognize the class as an entity
Check the following 3 points:
Try to annotate table (#table) with your concrete class NodoSemplice, not your abstract class NodoApprendimento.
It makes more sense to bind your table with your concrete class.
Make sure a table named nodi_app exists in the database you connected to, and the schema is public.
In your application.conf, enable Ebean:
ebean.default="models.*"

Categories