I have a test class which I use to check if the connection to the database is established. The credentials are saved in a properties file. When I run the tests in eclipse everything works fine. But when I run a maven build the tests fail because the username used to connect to the database is not the one I set in the properties file. It is the windows username. This is my code:
Properties File:
driverClassName=oracle.jdbc.driver.OracleDriver
user=database_dev1
password=password_dev1
url=jdbc:oracle:thin:#MyAwsomeDatabase:1521:DEV01
Config Class:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("de.xxx.bvd.mobisl.service")
#PropertySource("classpath:database.properties")
#ComponentScan("de.xxx.bvd.mobisl.service")
public class JPAConfig {
#Value("${driverClassName}")
protected String driverClassName;
#Value("${url}")
protected String url;
#Value("${user}")
protected String username;
#Value("${password}")
protected String password;
private static final Logger logger = Logger.getLogger(JPAConfig.class);
#SuppressWarnings("unchecked")
#Lazy
#Bean
public DataSource dataSource() {
try {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
Class<? extends Driver> driver = (Class<? extends Driver>) Class.forName(driverClassName);
dataSource.setDriverClass(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
logger.info("created DataSource with username " + username + " and password " + password);
return dataSource;
} catch (ClassNotFoundException e) {
logger.error("cannot create datasource!!", e);
return null;
}
}
As I said, running from eclipse works fine. The logfile says:
[[XXX-LOG]] 2018-09-04 08:27:23 INFO JPAConfig:57 - created DataSource with username database_dev1
[[XXX-LOG]] 2018-09-04 08:27:27 INFO JPAConfigTest:52 - got result from database
But running from maven the logfile says:
[[XXX-LOG]] 2018-09-04 08:27:53 INFO JPAConfig:57 - created DataSource with username <<Windows-Username>>
How can I tell maven to use the username from the properties file?
${user} is replaced by maven with the environment variable user.
You can get this if you run mvn help:system
Solution rename the property to be more specific like
db.username
A side effect user is very ambiguous in bigger projects. If you rename it it is more cleary where it is used
Related
Context
I am trying to start my spring app without a database(so when no database is available at initialization the app won't be stopped), i managed to do this with the following commands in app.prop:
#DB should not kill the app
spring.sql.init.continue-on-error=true //app should continue if a sql init error arrises
spring.liquibase.enabled=false // liquibase bean shouldn't be initialized at start up, without this command the app crashes anyway
spring.jpa.hibernate.ddl-auto=none
Now the only thing that i need to do is figure a way so when the app does make a successful connection with the db the liquibase migration files will get executed. For this task I understood I need to customize the liquibase bean, the following code shows my progress so far:
#Configuration
public class Config {
#Value("${postgres.host}")
private String host;
#Value("${postgres.port}")
private Integer port;
#Value("${postgres.database}")
private String database;
#Value("${postgres.user}")
private String user;
#Value("${postgres.password}")
private String password;
#Value("${spring.liquibase.change-log}")
private String changelog;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl(String.format("jdbc:postgresql://%s:%d/%s", host, port, database));
dataSource.setUsername(user);
dataSource.setPassword(password);
return dataSource;
}
#Bean
public SpringLiquibase liquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(dataSource());
liquibase.setChangeLog(changelog);
return liquibase;
}
}
Preferably if the database is down the bean should not be created and if the database is running/ the server established connection with the db at some point the bean will be brought in the context and execute the migration files, i don't know if that is possible as I am a newbie,but let me know if you have any suggestions.
I have an application with a UI which will manage some facets of a Spring Boot application while it is live.
First of all, there will be an INI file name passed in when the application starts which will have a username, password and host for the DB connection.
I have successfully implemented this dynamic database capability by starting the Spring Boot application after the initial INI file load.
However, I need to be able to change the #Primary data source on-the-fly during execution.
The user will click a button in the UI, the INI file will load from a specified source in the UI and I want the DB connection to drop, switch to the new properties read in from the INI file and restart the connection.
It seems to me that #RefreshScope + actuator method will not work for me since I am refreshing from a UI in the application and not an endpoint.
AbstractRoutingDatasource seems like it requires you to know the DB connection properties for the various sources at compile time and furthermore it's a lot more complex than I think is necessary to solve a simple problem such as this. I would think there should be some class which will allow a simple reload by telling it to call getDataSource again and reinitialize.
Configuration class:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "smsEntityManagerFactory",
transactionManagerRef = "smsTransactionManager",
basePackages = { "com.conceptualsystems.sms.db.repository" })
public class JpaConfig {
#Bean(name="SMSX")
#Primary
public DataSource getDataSource() {
Logger logger = LoggerFactory.getLogger(this.getClass());
logger.error("DATABASE INITIALIZING: getDataSource() called!");
DataSourceBuilder builder = DataSourceBuilder.create();
builder.driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
builder.username(IniSettings.getInstance().getIniFile().getDbUser());
builder.password(IniSettings.getInstance().getIniFile().getDbPassword());
String host = IniSettings.getInstance().getIniFile().getDbPath();
String db = IniSettings.getInstance().getIniFile().getDbName();
String connectionString = "jdbc:sqlserver://" + host + ";databaseName=" + db;
logger.info("Connecting [" + connectionString +"] as [" +
IniSettings.getInstance().getIniFile().getDbUser() + ":" +
IniSettings.getInstance().getIniFile().getDbPassword() + "]");
builder.url(connectionString);
return builder.build();
}
#Bean(name = "smsEntityManagerFactory")
#Primary
public LocalContainerEntityManagerFactoryBean smsEntityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("SMSX") DataSource dataSource) {
return builder
.dataSource(dataSource)
.persistenceUnit("smsEntityManagerFactory")
.packages("com.conceptualsystems.sms.db.entity")
.build();
}
#Bean(name = "smsTransactionManager")
#Primary
public PlatformTransactionManager smsTransactionManager(
#Qualifier("smsEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
main entry point:
public static void main(String[] args) {
try {
UIManager.setLookAndFeel( new FlatLightLaf() );
} catch( Exception ex ) {
System.err.println( "Failed to initialize LaF" );
}
javax.swing.SwingUtilities.invokeLater(() -> createAndShowSplash());
System.out.println("Scrap Management System v" + VERSION);
String iniFilename = null;
String user = null;
String pass = null;
for(String arg : args) {
if(arg.startsWith(ARG_HELP)) {
System.out.println(HELP_TEXT);
System.exit(0);
}
if(arg.startsWith(ARG_INI)) {
iniFilename = arg.substring(ARG_INI.length());
System.out.println("User entered INI file location on command line: " + iniFilename);
}
if(arg.startsWith(ARG_USER)) {
user = arg.substring(ARG_USER.length());
System.out.println("User entered DB username on command line: " + user);
}
if(arg.startsWith(ARG_PSWD)) {
pass = arg.substring(ARG_PSWD.length());
System.out.println("User entered DB password on command line: [****]");
}
}
mIniFile = new IniFile(iniFilename);
try {
mIniFile.load();
} catch (Exception e) {
System.out.println("Error loading INI file!");
e.printStackTrace();
}
IniSettings.getInstance().setIniFile(mIniFile);
System.out.println("INI file set completed, starting Spring Boot Application context...");
SplashFrame.getInstance().enableSiteSelection();
mApplicationContext = new SpringApplicationBuilder(Main.class)
.web(WebApplicationType.SERVLET)
.headless(false)
.bannerMode(Banner.Mode.LOG)
.run(args);
try {
IniSettings.getInstance().setIniSource(new IniJPA());
IniSettings.getInstance().load();
} catch(Exception e) {
System.out.println("Unable to setup INI from database!");
e.printStackTrace();
}
}
My db properties are kept in application-test.properties (I am running Springboot application in test profile) and the Datasource is referred through #Autowired annotation. It throws NullPointerException when I try to use datasource.getConnection().
I have referred similar questions and mostly all of them include some solutions with bean xml configurations. In my case I am not explicitly using any bean configurations. Every datasource properties are kept in application-test.properties file and I am referring through it using Datasource. I am a newbie to Springboot and any help would be great.
My repository class
#Repository
public class ActualUserDetailsDAO {
#Autowired
DataSource dataSource;
public String getPriorityType(String idNo) throws Exception {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
String cxPriorityType = null;
int count = 0;
try {
con = dataSource.getConnection();
String sql = ConfigurationHandler.getInstance().getConfigValue("sample.query");
......................
} catch (SQLException e) {
................
} catch (Exception e) {
..............
} finally {
.................
}
return cxPriorityType;
}
My application properties
spring.main.banner-mode=off
server.port=8180
# Datasource settings
spring.datasource.initialize=true
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.name=camst2
spring.datasource.url=jdbc:oracle:thin:#..................
spring.datasource.username=username
spring.datasource.password=password
# Tomcat JDBC settings
spring.datasource.tomcat.initial-size=10
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.min-idle=10
spring.datasource.tomcat.max-idle=100
#spring.datasource.tomcat.max-wait=6000
spring.datasource.tomcat.max-wait=30000
#spring.datasource.tomcat.test-on-connect=true
#spring.datasource.tomcat.test-on-borrow=true
#spring.datasource.tomcat.test-on-return=true
# Tomcat AccessLog
server.tomcat.accesslog.suffix=.log
server.tomcat.accesslog.prefix=access_log
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.directory=/tomcat/logs
server.tomcat.accesslog.pattern=%h %l %u %t %r %s %b %D
My application class
#SpringBootApplication
public class Application {
#Autowired
DataSource dataSource;
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
I found the solution. The problem was in my controller class. I was creating an instance of the my repository class by myself. I should have used #Autowired instead.
#RestController
public class ActualUserDetails implements ActualUserDetailsInt {
#RequestMapping(value = "/foo", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getActualUserDetails(#PathVariable("idNo") String idNo, #RequestParam("lob") String lob,
#RequestParam("offerSellingType") String offerSellingType) {
//do something
ActualUserDetailsDAO actualUserDetailsDAO = new ActualUserDetailsDAO();
actualUserDetailsDAO.getPriorityType(idNo);
//do something
I changed this into following.
#RestController
public class ActualUserDetails implements ActualUserDetailsInt {
#Autowired
ActualUserDetailsDAO actualUserDetailsDAO;
#RequestMapping(value = "/foo", method = RequestMethod.GET, produces =
MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getActualUserDetails(#PathVariable("idNo") String idNo,
#RequestParam("lob") String lob,
#RequestParam("offerSellingType") String offerSellingType) {
//do something
actualUserDetailsDAO.getPriorityType(idNo);
//do something
Manually creating object of my repository class did not detected dataSource defined inside it. Autowiring my repository class in my controller class seems to solve this problem.
If your data source is not been detected for any reason, I strongly recommend to have a deeper look on your code.
Following are some of the things to look for when this kind of error happens.
Look for the correct folder structure (application properties file
reside under resources folder)
If you are running Spring in a different profile (say test
profile), make sure relevant configurations are written in
application-test.properties
Check for proper annotation in relevant classes
Make sure your application properties are not overridden by any other
configurations
I have application properties file which I am dynamically updating using maven build step.
mvn clean -Dusername=user1 -Durl=xxxx -Dpassword=xxxx -DskipTests
install
jdbc.url=${url}
jdbc.username=${username}
jdbc.password=${password}
I am reading these properties in the configuration class
#Configuration
#ImportResource("classpath:/spring-beans.xml")
#PropertySource("classpath:/application.properties")
public class ApplicationConfiguration {
#Value("${jdbc.url}")
private String url;
#Value("${jdbc.username}")
private String username;
#Value("${jdbc.password}")
private String password;
#Bean(name = "c3p0DataSource")
public ComboPooledDataSource dataSource() throws PropertyVetoException,
IOException {
logger.info("Creating Datasource for {}",System.getenv("SPRING_DATASOURCE_URL"));
// logger.info("Creating Datasource for username {}",
prop.getProperty("username"));
logger.info("Creating Datasource for {}", System.getenv("username"));
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
logger.info("User Name :" + username);//returning $username instead of user1
logger.info("password :" + password);
System.out.println("User name : " + username);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource; } }
I am not getting updated values instead I am getting $username, $password as values, can anyone help me what i am missing here ?
my modified properties file look like below
jdbc.url=xxxx
jdbc.username=user1
jdbc.password=xxxx
you should run
mvn clean -Djdbc.username=user1 -Djdbc.url=xxxx -Djdbc.password=xxxx -DskipTests install
Rather than using Spring's application property , I will suggest you to use another property file, store it on file system and use org.apache.commons.configuration.PropertiesConfiguration class to load values from this file.
org.apache.commons.configuration.PropertiesConfiguration has capability to reload property file on change.
https://commons.apache.org/proper/commons-configuration/userguide/howto_properties.html
If you are using maven add below dependency.
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
When you say 'dynamically updated' it seems to me you just mean updated at build time and not at runtime. If so then you need to use the maven resources plugin, define the maven variables and use a different syntax in the properties file. This is covered in the properties and configuration section of the spring boot documentation
I have tried initialising data manually, which works. You can also give it a try.
You can try out the code below:
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Configuration;
#Configuration
#PropertySource("classpath:application.properties")
public class ApplicationConfiguration {
private Properties properties = new Properties();
public static String driverClass;
public static String dataSourceUrl;
public static String dataSourceUser;
public static String dataSourcePassword;
public ApplicationConfiguration() throws IOException {
properties.load(new InputStreamReader(ApplicationConfiguration.class.getResourceAsStream("/application.properties")));
driverClass = properties.getProperty("spring.datasource.driver-class-name");
dataSourceUrl = properties.getProperty("spring.datasource.url");
dataSourceUser = properties.getProperty("spring.datasource.username");
dataSourcePassword = properties.getProperty("spring.datasource.password");
}
// Other Code Details
}
Now I can easily use it like: ApplicationConfiguration.driverClass or ApplicationConfiguration.dataSourceUser.
Few other resources are also used by me from application.properties which I am not initialising manually and also not required while building jar. So only I am using #PropertySource("classpath:application.properties") to use other resources without initialising manually.
Try it once, It may help you :)
I tried unsuccessfully configure hikaricp and I don't see error in the code please help.
public class DatabaseManager {
private DatabaseClient[] databaseClients;
private HikariDataSource hikariDataSource;
public DatabaseManager(String absoluteFilePath) {
final HikariConfig hikariConfig = new HikariConfig(absoluteFilePath);
this.hikariDataSource = new HikariDataSource(hikariConfig);
System.out.println(hikariConfig.getUsername()); // null u-u
}
}
Properties file:
## Database Settings
dataSourceClassName=org.mariadb.jdbc.MySQLDataSource
dataSource.user=root
dataSource.password=
dataSource.databaseName=imagine-db
dataSource.portNumber=3306
dataSource.serverName=localhost
You've set the username on the data source not on the config itself. This will still work fine but you can't access it using hikariConfig.getUsername().
Try adding this to your properties file if you really need to access the user like that, although I suspect you don't.
username=root
password=