How to use TestContainers + Spring Boot + oracle-xe - spring

I try to use Test Containers with Oracle-XE module and Spring Boot and so far, when I launch my test, I am confronted to exception :
Caused by: java.lang.IllegalArgumentException: JDBC URL matches jdbc:tc: prefix but the database or tag name could not be identified
In my src/test/application.properties, I declared the url datatasource as :
spring.datasource.url=jdbc:tc:oracle-xe://somehostname:someport/databasename?TC_INITSCRIPT=schema-test.sql
To indicate the docker image to pull for oracle-xe, I created the file testcontainers.properties in src/test/resources :
oracle.container.image=oracleinanutshell/oracle-xe-11g:1.0.0
Do you have any idea how to make this work ?
It works flawlessly with MySQL, with the datasource url :
spring.datasource.url=jdbc:tc:mysql:5.6.23://somehostname:someport/databasename?TC_INITSCRIPT=schema-test.sql

You can make a test configuration class that redefine datasource bean with oracle xe container configuration.
public class OracleIT {
#ClassRule
public static OracleContainer oracleContainer = new OracleContainer();
#BeforeAll
public static void startup() {
oracleContainer.start();
}
#TestConfiguration
static class OracleTestConfiguration {
#Bean
DataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(oracleContainer.getJdbcUrl());
hikariConfig.setUsername(oracleContainer.getUsername());
hikariConfig.setPassword(oracleContainer.getPassword());
return new HikariDataSource(hikariConfig);
}
}
}

Related

How Jmx bean of Hikari cp can be used in spring boot when boot autoconfigure pooling

How Jmx bean of Hikari cp can be used in spring boot when boot autoconfigure pooling?
i tried following this instruction in below link.
https://github.com/brettwooldridge/HikariCP/wiki/MBean-(JMX)-Monitoring-and-Management.
Hre is my mbean class
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
#ManagedResource(
objectName="PD:category=MBeans,name=testBean",
description="Managed Bean")
#Component("testMbean")
public class HikariJmx {
private String message = "Simple Message";
private int size=0;
public HikariJmx(){
System.out.println("......TestMbean........");
}
#ManagedOperation
public void resetMessageViaMBean(){
this.message = "Message RESET";
}
#ManagedAttribute
public int getSize() throws Exception{
try {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName poolName = new ObjectName("com.zaxxer.hikari:type=Pool (HikariPool-1)");
HikariPoolMXBean poolProxy = JMX.newMXBeanProxy(mBeanServer, poolName, HikariPoolMXBean.class);
return poolProxy.getIdleConnections();
}catch(Exception e){e.printStackTrace();}
return 0;
}
}
Iam able to see the bean in visual vm. But when i take the attribute SIZE i am getting instanceNotFound exception in below line.
ObjectName poolName = new ObjectName("com.zaxxer.hikari:type=Pool (HikariPool-1)");
I am not creating datasource manually, just gave the properties in the application.properties and boot do the rest.
I had the same problem.
You can get the HikariPoolMXBean from the HikariDataSource itself.
In order to do that, I injected the HikariDataSource to the class I needed the HikariPoolMXBean, and called this method :
HikariPoolMXBean poolProxy = hikariDataSource.getHikariPoolMXBean();

When run spring boot tests got hazelcast.core.DuplicateInstanceNameException

How to execute integration tests of spring boot application with using Hazelcast, because when run all tests got hazelcast.core.DuplicateInstanceNameException?
I use Spring Boot 2.0.0.RC1 and Hazelcast 3.9.2
Use java configuration for hazelcast:
#Bean
public Config getHazelCastServerConfig() {
final Config config = new Config();
config.setInstanceName(hzInstance);
config.getGroupConfig().setName(hzGroupName).setPassword(hzGroupPassword);
final ManagementCenterConfig managementCenterConfig = config.getManagementCenterConfig();
managementCenterConfig.setEnabled(true);
managementCenterConfig.setUrl(mancenter);
final MapConfig mapConfig = new MapConfig();
mapConfig.setName(mapName);
mapConfig.setEvictionPolicy(EvictionPolicy.NONE);
mapConfig.setTimeToLiveSeconds(0);
mapConfig.setMaxIdleSeconds(0);
config.getScheduledExecutorConfig(scheduler)
.setPoolSize(16)
.setCapacity(100)
.setDurability(1);
final NetworkConfig networkConfig = config.getNetworkConfig();
networkConfig.setPort(5701);
networkConfig.setPortAutoIncrement(true).setPortCount(30);
final JoinConfig joinConfig = networkConfig.getJoin();
joinConfig.getMulticastConfig().setEnabled(false);
joinConfig.getAwsConfig().setEnabled(false);
final TcpIpConfig tcpIpConfig = joinConfig.getTcpIpConfig();
tcpIpConfig.addMember(memberOne)
.addMember(memberTwo);
tcpIpConfig.setEnabled(true);
return config;
}
#Bean
public HazelcastInstance getHazelCastServerInstance() {
final HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(getHazelCastServerConfig());
hazelcastInstance.getClientService().addClientListener(new ClientListener() {
#Override
public void clientConnected(Client client) {
System.out.println(String.format("Connected %s %s %s", client.getClientType(), client.getUuid(), client.getSocketAddress()));
log.info(String.format("Connected %s %s %s", client.getClientType(), client.getUuid(), client.getSocketAddress()));
}
#Override
public void clientDisconnected(Client client) {
System.out.println(String.format("Disconnected %s %s %s", client.getClientType(), client.getUuid(), client.getSocketAddress()));
log.info(String.format("Disconnected %s %s %s", client.getClientType(), client.getUuid(), client.getSocketAddress()));
}
});
return hazelcastInstance;
}
I have simple test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = UpaSdcApplication.class)
#ActiveProfiles("test")
public class CheckEndpoints {
#Autowired
private ApplicationContext context;
private static final String HEALTH_ENDPOINT = "/actuator/health";
private static WebTestClient testClient;
#Before
public void init() {
testClient = org.springframework.test.web.reactive.server.WebTestClient
.bindToApplicationContext(context)
.configureClient()
.filter(basicAuthentication())
.build();
}
#Test
public void testHealth(){
testClient.get().uri(HEALTH_ENDPOINT).accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectBody()
.json("{\"status\": \"UP\"}");
}
}
If run with test class separate from other tests - it execute fine and passes.
If run wiith other tests - get exception:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.hazelcast.core.HazelcastInstance]: Factory method 'getHazelCastServerInstance' threw exception; nested exception is com.hazelcast.core.DuplicateInstanceNameException: HazelcastInstance with name 'counter-instance' already exists!
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:579)
... 91 more
Caused by: com.hazelcast.core.DuplicateInstanceNameException: HazelcastInstance with name 'counter-instance' already exists!
at com.hazelcast.instance.HazelcastInstanceFactory.newHazelcastInstance(HazelcastInstanceFactory.java:170)
at com.hazelcast.instance.HazelcastInstanceFactory.newHazelcastInstance(HazelcastInstanceFactory.java:124)
at com.hazelcast.core.Hazelcast.newHazelcastInstance(Hazelcast.java:58)
at net.kyivstar.upa.sdc.config.HazelcastConfiguration.getHazelCastServerInstance(HazelcastConfiguration.java:84)
at net.kyivstar.upa.sdc.config.HazelcastConfiguration$$EnhancerBySpringCGLIB$$c7da65f3.CGLIB$getHazelCastServerInstance$0(<generated>)
at net.kyivstar.upa.sdc.config.HazelcastConfiguration$$EnhancerBySpringCGLIB$$c7da65f3$$FastClassBySpringCGLIB$$b920d5ef.invoke(<generated>)
How do you solve this problem? How do you run integration tests?
I had the same problem and I solved it checking if the instance already exists or not:
#Bean
public CacheManager cacheManager() {
HazelcastInstance existingInstance = Hazelcast.getHazelcastInstanceByName(CACHE_INSTANCE_NAME);
HazelcastInstance hazelcastInstance = null != existingInstance
? existingInstance
: Hazelcast.newHazelcastInstance(hazelCastConfig());
return new HazelcastCacheManager(hazelcastInstance);
}
You can see the rest of the code here.
instanceName configuration element is used to create a named Hazelcast member and should be unique for each Hazelcast instance in a JVM. In your case, either you should set a different instance name for each HazelcastInstance bean creation in the same JVM, or you can totally remove instanceName configuration if you don't recall instances by using instance name.
Had the same issue while running my tests. In my case reason was,that spring test framework was trying to launch new context, while keeping old one cached - thus trying to create another hazelcast instance with the same name, while one was already in the cached context.
Once the TestContext framework loads an ApplicationContext (or
WebApplicationContext) for a test, that context is cached and reused
for all subsequent tests that declare the same unique context
configuration within the same test suite.
Read here to understand more about how spring test framework manages test context.
I am working at the solution at the moment, will post it later. One possible solution I can see is droping test context after each test with #DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS), but this is very expensive in terms of performance.

Spring boot multiple datasources

I have a problem with an application using spring boot in 1.5.1 version.
My application need to communicate with 2 databases (Oracle and MySQL)
My application use 2 datasources :
- MySQL datasource
#Configuration
public class OracleDBConfig {
#Bean(name = "oracleDb")
#ConfigurationProperties(prefix = "spring.ds_oracle")
public DataSource oracleDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "oracleJdbcTemplate")
public JdbcTemplate oracleJdbcTemplate(#Qualifier("oracleDb") DataSource dsOracle) {
return new JdbcTemplate(dsOracle);
}
}
Oracle datasource
#Configuration
public class MySQLDBConfig {
#Bean(name = "mysqlDb")
#ConfigurationProperties(prefix = "spring.ds_mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "mysqlJdbcTemplate")
public JdbcTemplate mySQLjdbcTemplate(#Qualifier("mysqlDb")DataSource dsMySQL) {
return new JdbcTemplate(dsMySQL);
}
}
I have defined the 2 datasources in my applications.properties by using the prefix.
When I launch the program I have this error :
Parameter 0 of method oracleJdbcTemplate in com.bv.aircraft.config.OracleDBConfig required a single bean, but 2 were found:
- mysqlDb: defined by method 'mysqlDataSource' in class path resource [com/bv/aircraft/config/MySQLDBConfig.class]
- oracleDb: defined by method 'oracleDataSource' in class path resource [com/bv/aircraft/config/OracleDBConfig.class]
I have tried to use #Primary but it is not working when i need to use the other datasource.
Thank you
Add following in your spring boot configuration class
#EnableAutoConfiguration(exclude = {DataSourceTransactionManagerAutoConfiguration.class, DataSourceAutoConfiguration.class})
Sample Usage:
#SpringBootApplication
#EnableAutoConfiguration(exclude = {DataSourceTransactionManagerAutoConfiguration.class, DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Either:
separate the template configuration from the datasource configuration and inject in the two datasources with qualifiers into the template configuration, or just call the create datasource method directly, e.g.
public JdbcTemplate oracleJdbcTemplate(oracleDataSource()) DataSource dsOracle) {
return new JdbcTemplate(dsOracle);
}

Spring Boot externalised configuration for DataSource not working

I have an application - using Spring 4.3.6 and Spring Boot 1.4.4 - which will be exported as a JAR. I want to connect to a remote Oracle database but I am having trouble externalising the configuration without breaking the application.
This is my current workaround:
import org.apache.tomcat.jdbc.pool.DataSource;
#Bean
public DataSource dataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:oracle:thin:#ip-address:port:orcl");
dataSource.setUsername("user");
dataSource.setPassword("password");
dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
return dataSource;
}
With the above, my application is able to connect to the database and execute queries successfully. However, when I try to externalise the configuration as follows:
#Bean
#ConfigurationProperties(prefix="app.datasource")
public DataSource dataSource() {
return new DataSource();
}
// application.properties
app.datasource.url=jdbc:oracle:thin:#ip-address:port:orcl
app.datasource.username=user
app.datasource.password=password
app.datasource.driver-class-name=oracle.jdbc.OracleDriver
I will get the following error when trying to execute jdbcTemplate.update(query) in my Spring Boot Controller (note that without externalising the above works):
org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: The url cannot be null
I have tried to remove #ConfigurationProperties and change app.datasource to spring.datasource. I have also tried to use DataSourceBuilder.create().build() which returns javax.sql.DataSource but the same error is thrown in both cases.
I'm doing something wrong. What's the correct way to successfully externalise the configuration?
Suppose you have two datasources for two different Oracle databases. Then you have the following properties file:
/path/to/config/application.properties
oracle1.username=YourUser1
oracle1.password=YourPassword1
oracle1.url=jdbc:oracle:thin:#localhost:1521:XE
oracle2.username=YourUser2
oracle2.password=YourPassword2
oracle2.url=jdbc:oracle:thin:#192.168.0.3:1521:XE
Then in a configuration file:
import oracle.jdbc.pool.OracleDataSource;
#Configuration
public class DatasourcesConfig {
#Autowired
private Environment env;
#Primary
#Bean(name = "dataSource1")
DataSource oracle1DataSource() throws SQLException {
OracleDataSource dataSource = new OracleDataSource();
dataSource.setUser(env.getProperty("oracle1.username"));
dataSource.setPassword(env.getProperty("oracle1.password"));
dataSource.setURL(env.getProperty("oracle1.url"));
return dataSource;
}
#Bean(name = "dataSource2")
DataSource oracle2DataSource() throws SQLException {
OracleDataSource dataSource = new OracleDataSource();
dataSource.setUser(env.getProperty("oracle2.username"));
dataSource.setPassword(env.getProperty("oracle2.password"));
dataSource.setURL(env.getProperty("oracle2.url"));
return dataSource;
}
}
If you want to specify the external location of your application.properties file when running the jar, then set the spring.config.location as a system property, you can try:
java -jar target/your-application-0.0.1.jar -Dspring.config.location=/path/to/config/
Make sure the application.properties file is excluded when building the jar
There should be no need to create the DataSourceyourself.
Make sure you have the
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
Dependecy in your classpath and the oracle driver and put following properties in your application.properties file:
spring.datasource.url=jdbc:oracle:thin:#ip-address:port:orcl
spring.datasource.username=user
spring.datasource.password=password
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
After that you should be able to #Autowired your DataSource
For more information have a look at:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database
You cannot override the predefined properties provided by spring boot.
Just use the following properties in application.properties file.
spring.datasource.url=jdbc:oracle:thin:#ip-address:port:orcl
spring.datasource.data-username=user
spring.datasource.data-password=password
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
See Also : https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
Apart from above, to clarify #ConfigurationProperties is used at class level and the prefix "app" not "app.datasource"
#ConfigurationProperties(prefix = "app")
Now you have a class named DbProperties and the properties of the class is same as the last part of the key in application.properties
public class DBProperties {
private String url;
private String username;
private String password;
// setters and getters
}
Now the actual config/component class should look like following.
#Component
#ConfigurationProperties(prefix = "app")
public class MyComponent {
DBProperties datasource = new DBProperties();
public DBProperties getDatasource() {
return datasource;
}
public void setDatasource(DBProperties datasource) {
this.datasource = datasource;
}
}
Please note
The instance variable name is datasource which is same as the second part of the key
datasource is a class level instance

Spring Boot: Web.xml and embedded server jar

I'm trying to convert a legacy spring-mvc app to Spring boot (in order to have a self contained JAR enabling easier upgrade to Java-8).
I see no reason to use replace my existing web.xml file with code as the code looks like configuration and web.xml is more established.
Is it possible to use my existing web.xml in a Spring Boot application (in embedded JAR mode)?
Edit: I also want to avoid using #EnableAutoConfiguration
Thanks
ok, thanks to Mecon, I'm slightly closer. I had to remove the ContextLoaderListener in the web.xml; also had to import the xml Spring config even though it was referenced in the contextConfigLocation.
#Configuration
#ComponentScan
#EnableAutoConfiguration
#ImportResource(value = {"classpath:/webapp-base.xml"})
public class WebApp {
#Autowired
private ServerProperties serverProperties;
#Autowired
private MediaConfiguration mediaConfig;
#Bean
public EmbeddedServletContainerFactory servletContainer() {
JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory();
factory.setContextPath(serverProperties.getContextPath());
factory.addConfigurations(new WebXmlConfiguration());
factory.addServerCustomizers(server -> {
List<Handler> resourceHandlers = getResourceHandlers();
Handler original = server.getHandler();
HandlerList handlerList = new HandlerList();
Handler[] array = getHandlers(original, resourceHandlers);
handlerList.setHandlers(array);
server.setHandler(handlerList);
}
);
return factory;
}
private List<Handler> getResourceHandlers() {
return mediaConfig.getMappings().stream().map(m -> {
ContextHandler contextHandler = new ContextHandler(m.getUrlpath());
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase(m.getFilepath());
contextHandler.setHandler(resourceHandler);
return contextHandler;
}).collect(Collectors.toList());
}
private Handler[] getHandlers(Handler original, List<Handler> resourceHandlers) {
ArrayList<Handler> handlers = new ArrayList<>();
handlers.add(original);
handlers.addAll(resourceHandlers);
return handlers.toArray(new Handler[resourceHandlers.size()+1]);
}
public static void main(String[] args) {
SpringApplication.run(WebApp.class, args);
}
}
You don't need Spring-Boot to have a self-contained JAR, all you really need is Embedded Tomcat, or Jetty.
Create a class with public static void main(String[] a), and this Class will be used when the Jar is "executed" by java -jar myWebapp.jar command.
In the main method, you can fire up the Embedded Tomcat or Jetty, and make it load your webapp by referring to existing web.xml.

Resources